# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE

import datetime
import re

from dateutil.parser import ParserError

import frappe
from frappe.model.meta import get_field_currency, get_field_precision
from frappe.utils import (
	cint,
	cstr,
	flt,
	fmt_money,
	format_datetime,
	format_duration,
	format_time,
	format_timedelta,
	formatdate,
)

BLOCK_TAGS_PATTERN = re.compile(r"(<br|<div|<p)")


def format_value(value, df=None, doc=None, currency=None, translated=False, format=None):
	"""
	Format value based on given fieldtype, document reference, currency reference.
	If docfield info (df) is not given, it will try and guess based on the datatype of the value.

	:param value: Value to be formatted.
	:param df: (Optional) DocField object with properties `fieldtype`, `options` etc.
	"""
	if isinstance(df, str):
		df = frappe._dict(fieldtype=df)

	if not df:
		df = frappe._dict()
		match value:
			case datetime.datetime():
				df.fieldtype = "Datetime"
			case datetime.date():
				df.fieldtype = "Date"
			case datetime.timedelta():
				df.fieldtype = "Time"
			case int():
				df.fieldtype = "Int"
			case float():
				df.fieldtype = "Float"
			case _:
				df.fieldtype = "Data"

	elif isinstance(df, dict):
		# Convert dict to object if necessary
		df = frappe._dict(df)

	if value is None:
		value = ""
	elif translated:
		value = frappe._(value)

	if not df:
		return value

	elif df.get("fieldtype") == "Date":
		return formatdate(value)

	elif df.get("fieldtype") == "Datetime":
		return format_datetime(value)

	elif df.get("fieldtype") == "Time":
		try:
			return format_time(value)
		except ParserError:
			return format_timedelta(value)

	elif (
		value == 0
		and df.get("fieldtype") in ("Int", "Float", "Currency", "Percent")
		and df.get("print_hide_if_no_value")
	):
		# this is required to show 0 as blank in table columns
		return ""

	elif df.get("fieldtype") == "Currency":
		default_currency = frappe.db.get_default("currency")
		currency = currency or get_field_currency(df, doc) or default_currency
		return fmt_money(value, precision=get_field_precision(df, doc), currency=currency, format=format)

	elif df.get("fieldtype") == "Float":
		precision = get_field_precision(df, doc)
		# I don't know why we support currency option for float
		currency = currency or get_field_currency(df, doc)

		return fmt_money(value, precision=precision, currency=currency)

	elif df.get("fieldtype") == "Percent":
		return f"{flt(value, 2)}%"

	elif df.get("fieldtype") in ("Text", "Small Text"):
		if not BLOCK_TAGS_PATTERN.search(value):
			return frappe.safe_decode(value).replace("\n", "<br>")

	elif df.get("fieldtype") == "Markdown Editor":
		return frappe.utils.markdown(value)

	elif df.get("fieldtype") == "Table MultiSelect":
		values = []
		meta = frappe.get_meta(df.options)
		link_field = next(df for df in meta.fields if df.fieldtype == "Link")
		for v in value:
			v.update({"__link_titles": doc.get("__link_titles")})
			formatted_value = format_value(v.get(link_field.fieldname, ""), link_field, v)
			values.append(formatted_value)

		return ", ".join(values)

	elif df.get("fieldtype") == "Duration":
		hide_days = df.hide_days
		return format_duration(value, hide_days)

	elif df.get("fieldtype") == "Text Editor":
		return f"<div class='ql-snow'>{value}</div>"

	elif df.get("fieldtype") in ["Link", "Dynamic Link"]:
		if not doc or not doc.get("__link_titles") or not df.options:
			return value

		doctype = df.options
		if df.get("fieldtype") == "Dynamic Link":
			if not df.parent:
				return value

			meta = frappe.get_meta(df.parent)
			_field = meta.get_field(df.options)
			doctype = _field.options
		return doc.__link_titles.get(f"{doctype}::{value}", value)

	elif df.get("fieldtype") == "Select":
		if isinstance(value, str):
			return frappe._(value, context=df.parent or "")

	return value
