import {
  buildCodecForObject,
  buildCodecForUnion,
  Codec,
  codecForBoolean,
  codecForConstString,
  codecForLazy,
  codecForList,
  codecForNumber,
  codecForString,
  codecForTimestamp,
  codecOptional,
  Integer,
  TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";

export type FormConfiguration = DoubleColumnForm;

export type DoubleColumnForm = {
  type: "double-column";
  design: DoubleColumnFormSection[];
  // behavior?: (form: Partial<T>) => FormState<T>;
};

export type DoubleColumnFormSection = {
  title: string;
  description?: string;
  fields: UIFormElementConfig[];
};

// export interface BaseForm {
//   state: TalerExchangeApi.AmlState;
//   threshold: AmountJson;
// }

export type UIFormElementConfig =
  | UIFormElementGroup
  | UIFormElementCaption
  | UIFormFieldAbsoluteTime
  | UIFormFieldAmount
  | UIFormFieldArray
  | UIFormFieldChoiseHorizontal
  | UIFormFieldChoiseStacked
  | UIFormFieldFile
  | UIFormFieldInteger
  | UIFormFieldSelectMultiple
  | UIFormFieldSelectOne
  | UIFormFieldText
  | UIFormFieldTextArea
  | UIFormFieldToggle;

type UIFormFieldAbsoluteTime = {
  type: "absoluteTimeText";
  max?: TalerProtocolTimestamp;
  min?: TalerProtocolTimestamp;
  pattern: string;
} & UIFormFieldBaseConfig;

type UIFormFieldAmount = {
  type: "amount";
  max?: Integer;
  min?: Integer;
  currency: string;
} & UIFormFieldBaseConfig;

type UIFormFieldArray = {
  type: "array";
  // id of the field shown when the array is collapsed
  labelFieldId: UIHandlerId;
  fields: UIFormElementConfig[];
} & UIFormFieldBaseConfig;

type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;

type UIFormElementGroup = {
  type: "group";
  fields: UIFormElementConfig[];
} & UIFieldElementDescription;

type UIFormFieldChoiseHorizontal = {
  type: "choiceHorizontal";
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

type UIFormFieldChoiseStacked = {
  type: "choiceStacked";
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

type UIFormFieldFile = {
  type: "file";
  maxBytes?: Integer;
  minBytes?: Integer;
  // comma-separated list of one or more file types
  // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept#unique_file_type_specifiers
  accept?: string;
} & UIFormFieldBaseConfig;

type UIFormFieldInteger = {
  type: "integer";
  max?: Integer;
  min?: Integer;
} & UIFormFieldBaseConfig;

interface SelectUiChoice {
  label: string;
  description?: string;
  value: string;
}

type UIFormFieldSelectMultiple = {
  type: "selectMultiple";
  max?: Integer;
  min?: Integer;
  unique?: boolean;
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;

type UIFormFieldSelectOne = {
  type: "selectOne";
  choices: Array<SelectUiChoice>;
} & UIFormFieldBaseConfig;
type UIFormFieldText = { type: "text" } & UIFormFieldBaseConfig;
type UIFormFieldTextArea = { type: "textArea" } & UIFormFieldBaseConfig;
type UIFormFieldToggle = { type: "toggle" } & UIFormFieldBaseConfig;

export type UIFieldElementDescription = {
  /* label if the field, visible for the user */
  label: string;

  /* long text to be shown on user demand */
  tooltip?: string;

  /* short text to be shown close to the field, usually below and dimmer*/
  help?: string;

  /* name of the field, useful for a11y */
  name: string;

  /* if the field should be initially hidden */
  hidden?: boolean;

  /* ui element to show before */
  addonBeforeId?: string;

  /* ui element to show after */
  addonAfterId?: string;
};

export type UIFormFieldBaseConfig = UIFieldElementDescription & {
  /* example to be shown inside the field */
  placeholder?: string;

  /* show a mark as required */
  required?: boolean;

  /* readonly and dim */
  disabled?: boolean;

  /* conversion id to convert the string into the value type
      the id should be known to the ui impl
   */
  converterId?: string;

  /* property id of the form */
  id: UIHandlerId;
};

declare const __handlerId: unique symbol;
export type UIHandlerId = string & { [__handlerId]: true };

// FIXME: validate well formed ui field id
const codecForUiFieldId = codecForString as () => Codec<UIHandlerId>;

const codecForUIFormFieldBaseDescriptionTemplate = <
  T extends UIFieldElementDescription,
>() =>
  buildCodecForObject<T>()
    .property("addonAfterId", codecOptional(codecForString()))
    .property("addonBeforeId", codecOptional(codecForString()))
    .property("hidden", codecOptional(codecForBoolean()))
    .property("help", codecOptional(codecForString()))
    .property("label", codecForString())
    .property("name", codecForString())
    .property("tooltip", codecOptional(codecForString()));

const codecForUIFormFieldBaseConfigTemplate = <
  T extends UIFormFieldBaseConfig,
>() =>
  codecForUIFormFieldBaseDescriptionTemplate<T>()
    .property("id", codecForUiFieldId())
    .property("converterId", codecOptional(codecForString()))
    .property("disabled", codecOptional(codecForBoolean()))
    .property("required", codecOptional(codecForBoolean()))
    .property("placeholder", codecOptional(codecForString()));

const codecForUiFormFieldAbsoluteTime = (): Codec<UIFormFieldAbsoluteTime> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldAbsoluteTime>()
    .property("type", codecForConstString("absoluteTimeText"))
    .property("pattern", codecForString())
    .property("max", codecOptional(codecForTimestamp))
    .property("min", codecOptional(codecForTimestamp))
    .build("UIFormFieldAbsoluteTime");

const codecForUiFormFieldAmount = (): Codec<UIFormFieldAmount> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldAmount>()
    .property("type", codecForConstString("amount"))
    .property("currency", codecForString())
    .property("max", codecOptional(codecForNumber()))
    .property("min", codecOptional(codecForNumber()))
    .build("UIFormFieldAmount");

const codecForUiFormFieldArray = (): Codec<UIFormFieldArray> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldArray>()
    .property("type", codecForConstString("array"))
    .property("labelFieldId", codecForUiFieldId())
    .property("tooltip", codecOptional(codecForString()))
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    .property("fields", codecForList(codecForUiFormField()))
    .build("UIFormFieldArray");

const codecForUiFormFieldCaption = (): Codec<UIFormElementCaption> =>
  codecForUIFormFieldBaseDescriptionTemplate<UIFormElementCaption>()
    .property("type", codecForConstString("caption"))
    .build("UIFormFieldCaption");

const codecForUiFormSelectUiChoice = (): Codec<SelectUiChoice> =>
  buildCodecForObject<SelectUiChoice>()
    .property("description", codecOptional(codecForString()))
    .property("label", codecForString())
    .property("value", codecForString())
    .build("SelectUiChoice");

const codecForUiFormFieldChoiceHorizontal =
  (): Codec<UIFormFieldChoiseHorizontal> =>
    codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseHorizontal>()
      .property("type", codecForConstString("choiceHorizontal"))
      .property("choices", codecForList(codecForUiFormSelectUiChoice()))
      .build("UIFormFieldChoiseHorizontal");

const codecForUiFormFieldChoiceStacked = (): Codec<UIFormFieldChoiseStacked> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldChoiseStacked>()
    .property("type", codecForConstString("choiceStacked"))
    .property("choices", codecForList(codecForUiFormSelectUiChoice()))
    .build("UIFormFieldChoiseStacked");

const codecForUiFormFieldFile = (): Codec<UIFormFieldFile> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldFile>()
    .property("type", codecForConstString("file"))
    .property("accept", codecOptional(codecForString()))
    .property("maxBytes", codecOptional(codecForNumber()))
    .property("minBytes", codecOptional(codecForNumber()))
    .build("UIFormFieldFile");

const codecForUiFormFieldGroup = (): Codec<UIFormElementGroup> =>
  codecForUIFormFieldBaseDescriptionTemplate<UIFormElementGroup>()
    .property("type", codecForConstString("group"))
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    .property("fields", codecForList(codecForUiFormField()))
    .build("UiFormFieldGroup");

const codecForUiFormFieldInteger = (): Codec<UIFormFieldInteger> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldInteger>()
    .property("type", codecForConstString("integer"))
    // .property("properties", codecForUIFormFieldBaseConfig())
    .property("max", codecOptional(codecForNumber()))
    .property("min", codecOptional(codecForNumber()))
    .build("UIFormFieldInteger");

const codecForUiFormFieldSelectMultiple =
  (): Codec<UIFormFieldSelectMultiple> =>
    codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectMultiple>()
      .property("type", codecForConstString("selectMultiple"))
      .property("max", codecOptional(codecForNumber()))
      .property("min", codecOptional(codecForNumber()))
      .property("unique", codecOptional(codecForBoolean()))
      .property("choices", codecForList(codecForUiFormSelectUiChoice()))
      .build("UiFormFieldSelectMultiple");

const codecForUiFormFieldSelectOne = (): Codec<UIFormFieldSelectOne> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldSelectOne>()
    .property("type", codecForConstString("selectOne"))
    .property("choices", codecForList(codecForUiFormSelectUiChoice()))
    .build("UIFormFieldSelectOne");

const codecForUiFormFieldText = (): Codec<UIFormFieldText> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldText>()
    .property("type", codecForConstString("text"))
    .build("UIFormFieldText");

const codecForUiFormFieldTextArea = (): Codec<UIFormFieldTextArea> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldTextArea>()
    .property("type", codecForConstString("textArea"))
    .build("UIFormFieldTextArea");

const codecForUiFormFieldToggle = (): Codec<UIFormFieldToggle> =>
  codecForUIFormFieldBaseConfigTemplate<UIFormFieldToggle>()
    .property("type", codecForConstString("toggle"))
    .build("UIFormFieldToggle");

const codecForUiFormField = (): Codec<UIFormElementConfig> =>
  buildCodecForUnion<UIFormElementConfig>()
    .discriminateOn("type")
    .alternative("array", codecForLazy(codecForUiFormFieldArray))
    .alternative("group", codecForLazy(codecForUiFormFieldGroup))
    .alternative("absoluteTimeText", codecForUiFormFieldAbsoluteTime())
    .alternative("amount", codecForUiFormFieldAmount())
    .alternative("caption", codecForUiFormFieldCaption())
    .alternative("choiceHorizontal", codecForUiFormFieldChoiceHorizontal())
    .alternative("choiceStacked", codecForUiFormFieldChoiceStacked())
    .alternative("file", codecForUiFormFieldFile())
    .alternative("integer", codecForUiFormFieldInteger())
    .alternative("selectMultiple", codecForUiFormFieldSelectMultiple())
    .alternative("selectOne", codecForUiFormFieldSelectOne())
    .alternative("text", codecForUiFormFieldText())
    .alternative("textArea", codecForUiFormFieldTextArea())
    .alternative("toggle", codecForUiFormFieldToggle())
    .build("UIFormField");

const codecForDoubleColumnFormSection = (): Codec<DoubleColumnFormSection> =>
  buildCodecForObject<DoubleColumnFormSection>()
    .property("title", codecForString())
    .property("description", codecOptional(codecForString()))
    .property("fields", codecForList(codecForUiFormField()))
    .build("DoubleColumnFormSection");

const codecForDoubleColumnForm = (): Codec<DoubleColumnForm> =>
  buildCodecForObject<DoubleColumnForm>()
    .property("type", codecForConstString("double-column"))
    .property("design", codecForList(codecForDoubleColumnFormSection()))
    .build("DoubleColumnForm");

const codecForFormConfiguration = (): Codec<FormConfiguration> =>
  buildCodecForUnion<FormConfiguration>()
    .discriminateOn("type")
    .alternative("double-column", codecForDoubleColumnForm())
    .build<FormConfiguration>("FormConfiguration");

const codecForFormMetadata = (): Codec<FormMetadata> =>
  buildCodecForObject<FormMetadata>()
    .property("label", codecForString())
    .property("id", codecForString())
    .property("version", codecForNumber())
    .property("config", codecForFormConfiguration())
    .build("FormMetadata");

export const codecForUIForms = (): Codec<UiForms> =>
  buildCodecForObject<UiForms>()
    .property("forms", codecForList(codecForFormMetadata()))
    .build("UiForms");

export type FormMetadata = {
  label: string;
  id: string;
  version: number;
  config: FormConfiguration;
};

export interface UiForms {
  // Where libeufin backend is localted
  // default: window.origin without "webui/"
  forms: Array<FormMetadata>;
}
