import {
  AbsoluteTime,
  AmountJson,
  Amounts,
  assertUnreachable,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { format, parse } from "date-fns";
import { FormModel } from "../hooks/useForm.js";
import {
  InternationalizationAPI,
  UIFieldElementDescription,
} from "../index.browser.js";
import { UIFormField } from "./field-types.js";
import { Addon, StringConverter, UIFieldHandler } from "./FormProvider.js";
import { UIFormElementConfig, UIFormFieldBaseConfig } from "./forms-types.js";

/**
 * convert field configuration to render function
 * FIXME: change this mapping for something not so insane
 *
 * @param i18n_
 * @param fieldConfig
 * @param formModel
 * @returns
 */
export function convertFormConfigToUiField(
  i18n_: InternationalizationAPI,
  parentKey: string | number,
  fieldConfig: UIFormElementConfig[],
  formModel: FormModel,
): UIFormField[] {
  const result = fieldConfig.map((config, fieldIndex) => {
    if (config.type === "void") return undefined;
    const uiKey = `${parentKey}.${fieldIndex}`;
    const handler = formModel.getHandlerForUiField(uiKey);
    const name = handler.name;
    // FIXME: first computed prop, all should be computed
    const hidden =
      config.hidden === true
        ? true
        : config.hide
          ? config.hide(handler.value, handler.formRootResult)
          : undefined;

    switch (config.type) {
      case "htmlIframe": {
        const resp: UIFormField = {
          type: config.type,
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            url: config.url,
          },
        };
        return resp;
      }
      case "group": {
        const resp: UIFormField = {
          type: config.type,
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            fields: convertFormConfigToUiField(
              i18n_,
              `${parentKey}.${fieldIndex}`,
              config.fields,
              formModel,
            ),
          },
        };
        return resp;
      }
      case "caption": {
        const resp: UIFormField = {
          type: config.type,
          properties: { ...convertBaseFieldsProps(i18n_, config), hidden },
        };
        return resp;
      }
      case "array": {
        return {
          type: config.type,
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            labelField: config.labelFieldId,
            fields: config.fields,
            hidden,
          },
        } as UIFormField;
      }
      case "external-link": {
        return {
          type: config.type,
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            label: i18n_.str`${config.label}`,
            url: config.url,
            media: config.media,
          },
        };
      }
      case "download-link": {
        return {
          type: config.type,
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            label: i18n_.str`${config.label}`,
            url: config.url,
            media: config.media,
          },
        };
        // return resp;
      }
      case "absoluteTimeText": {
        return {
          type: "absoluteTimeText",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "isoDateText": {
        return {
          type: "isoDateText",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            pattern: config.pattern,
            defaultValue: config.defaultValue,
            calendarDefaultValue: config.defaultCalendarValue,
            hidden,
          },
        } as UIFormField;
      }
      case "amount": {
        return {
          type: "amount",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            currency: config.currency,
          },
        } as UIFormField;
      }
      case "choiceHorizontal": {
        return {
          type: "choiceHorizontal",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            choices: config.choices,
          },
        } as UIFormField;
      }
      case "drilldown": {
        return {
          type: "drilldown",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            choices: config.choices,
          },
        } as UIFormField;
      }
      case "choiceStacked": {
        return {
          type: "choiceStacked",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            choices: config.choices,
          },
        } as UIFormField;
      }
      case "file": {
        return {
          type: "file",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            accept: config.accept,
            maxBites: config.maxBytes,
          },
        } as UIFormField;
      }
      case "integer": {
        return {
          type: "integer",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "secret": {
        return {
          type: "secret",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "selectMultiple": {
        return {
          type: "selectMultiple",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
            choices: config.choices,
            unique: config.unique,
          },
        } as UIFormField;
      }
      case "selectOne": {
        return {
          type: "selectOne",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            preferredChoiceVals: config.preferredChoiceVals,
            hidden,
            choices: config.choices,
          },
        } as UIFormField;
      }
      case "text": {
        return {
          type: "text",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "textArea": {
        return {
          type: "textArea",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "duration": {
        return {
          type: "duration",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "durationText": {
        return {
          type: "durationText",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            hidden,
          },
        } as UIFormField;
      }
      case "toggle": {
        return {
          type: "toggle",
          properties: {
            ...convertBaseFieldsProps(i18n_, config),
            ...convertInputFieldsProps(
              name,
              handler,
              config,
              getConverterByFieldType(config.type, config),
            ),
            threeState: config.threeState,
            trueValue: config.trueValue,
            falseValue: config.falseValue,
            onlyTrueValue: config.onlyTrueValue,
            hidden,
          },
        } as UIFormField;
      }
      default: {
        assertUnreachable(config);
      }
    }
  });
  return result.filter((v): v is UIFormField => !!v);
}

function getAddonById(_id: string | undefined): Addon {
  return undefined!;
}

function getConverterByFieldType(
  fieldType: string | undefined,
  config: unknown,
): StringConverter<unknown> {
  if (fieldType === "absoluteTimeText") {
    // @ts-expect-error check this
    return absTimeConverter(config);
  }
  if (fieldType === "amount") {
    // @ts-expect-error check this
    return amountConverter(config);
  }
  return nullConverter as StringConverter<unknown>;
}

/**
 * Input field take most of the properties from the
 * handler, since the input value can change the
 * some states like hidden or disabled.
 *
 * @param form
 * @param config
 * @param converter
 * @returns
 */
function convertInputFieldsProps(
  name: string | undefined,
  handler: UIFieldHandler,
  config: UIFormFieldBaseConfig,
  converter: StringConverter<unknown>,
) {
  return {
    converter,
    handler,
    name,
    hidden: config.hidden,
    required: config.required,
    disabled: config.disabled,
    help: config.help,
    placeholder: config.placeholder,
    tooltip: config.tooltip,
    label: config.label as TranslatedString,
  };
}

function convertBaseFieldsProps(
  i18n_: InternationalizationAPI,
  p: UIFieldElementDescription,
) {
  return {
    after: getAddonById(p.addonAfterId),
    before: getAddonById(p.addonBeforeId),
    help: i18n_.str`${p.help}`,
    label: i18n_.str`${p.label}`,
    tooltip: i18n_.str`${p.tooltip}`,
  };
}

const nullConverter: StringConverter<string> = {
  fromStringUI(v: string | undefined): string {
    return v ?? "";
  },
  toStringUI(v: unknown): string {
    return v as string;
  },
};

function amountConverter(config: any): StringConverter<AmountJson> {
  const currency = config["currency"];
  if (!currency || typeof currency !== "string") {
    throw Error(`amount converter needs a currency`);
  }
  return {
    fromStringUI(v: string | undefined): AmountJson {
      return (
        Amounts.parse(`${currency}:${v}`) ?? Amounts.zeroOfCurrency(currency)
      );
    },
    toStringUI(v: unknown): string {
      return v === undefined ? "" : Amounts.stringifyValue(v as AmountJson);
    },
  };
}

function absTimeConverter(config: any): StringConverter<AbsoluteTime> {
  const pattern = config["pattern"];
  if (!pattern || typeof pattern !== "string") {
    throw Error(`absTime converter needs a pattern`);
  }
  return {
    fromStringUI(v: string | undefined): AbsoluteTime {
      if (v === undefined) {
        return AbsoluteTime.never();
      }
      try {
        const time = parse(v, pattern, new Date());
        return AbsoluteTime.fromMilliseconds(time.getTime());
      } catch (e) {
        return AbsoluteTime.never();
      }
    },
    toStringUI(v: unknown): string {
      if (v === undefined) return "";
      const d = v as AbsoluteTime;
      if (d.t_ms === "never") return "never";
      try {
        return format(d.t_ms, pattern);
      } catch (e) {
        return "";
      }
    },
  };
}
