/*
 This file is part of GNU Taler
 (C) 2022-2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
import {
  AbsoluteTime,
  AccessToken,
  Amounts,
  HttpStatusCode,
  KycRequirementInformation,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Button,
  FormMetadata,
  InternationalizationAPI,
  LocalNotificationBanner,
  RenderAllFieldsByUiConfig,
  UIFormElementConfig,
  UIHandlerId,
  convertUiField,
  getConverterById,
  useExchangeApiContext,
  useLocalNotificationHandler,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { preloadedForms } from "../forms/index.js";
import {
  FormErrors,
  useFormState,
  validateRequiredFields,
} from "../hooks/form.js";
import { undefinedIfEmpty } from "./Start.js";
import { useUiFormsContext } from "../context/ui-forms.js";

type Props = {
  token: AccessToken;
  formId: string;
  requirement: KycRequirementInformation;
  onComplete: () => void;
};

type FormType = {
  // state: TalerExchangeApi.AmlState;
};

type KycFormMetadata = {
  id: string;
  version: number;
  when: AbsoluteTime;
};

type KycForm = {
  header: KycFormMetadata;
  payload: object;
};

export function FillForm({
  token,
  formId,
  requirement,
  onComplete,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const { config, lib } = useExchangeApiContext();
  // const { forms } = useUiFormsContext();
  const [notification, withErrorHandler] = useLocalNotificationHandler();

  const customForm =
    requirement.context && "form" in requirement.context
      ? ({
          id: (requirement.context.form as any).id,
          config: requirement.context.form,
          label: "Officer defined form",
          version: 1,
        } as FormMetadata)
      : undefined;

  const { forms } = useUiFormsContext();
  const allForms = customForm ? [...forms, customForm] : forms
  const theForm = searchForm(
    i18n,
    allForms,
    formId,
    requirement.context,
  );
  if (!theForm) {
    return <div>form with id {formId} not found</div>;
  }
  const reqId = requirement.id;
  if (!reqId) {
    return <div>no id for this form, can't upload</div>;
  }
  const shape: Array<UIHandlerId> = [];
  const requiredFields: Array<UIHandlerId> = [];

  theForm.config.design.forEach((section) => {
    Array.prototype.push.apply(shape, getShapeFromFields(section.fields));
    Array.prototype.push.apply(
      requiredFields,
      getRequiredFields(section.fields),
    );
  });
  const [form, state] = useFormState<FormType>(shape, {}, (st) => {
    const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({});

    const errors = undefinedIfEmpty<FormErrors<FormType> | undefined>(
      validateRequiredFields(partialErrors, st, requiredFields),
    );

    if (errors === undefined) {
      return {
        status: "ok",
        result: st as any,
        errors: undefined,
      };
    }

    return {
      status: "fail",
      result: st as any,
      errors,
    };
  });
  const validatedForm = state.status !== "ok" ? undefined : state.result;

  const submitHandler =
    validatedForm === undefined
      ? undefined
      : withErrorHandler(
          async () => {
            const information: KycForm = {
              header: {
                id: theForm.id,
                version: theForm.version,
                when: AbsoluteTime.now(),
              },
              payload: validatedForm,
            };

            // const data = new FormData()
            // data.set("header", JSON.stringify(information.header))
            // data.set("payload", JSON.stringify(information.payload))
            const data = new URLSearchParams();
            Object.entries(validatedForm as Record<string, string>).forEach(
              ([key, value]) => {
                if (key === "money") {
                  data.set(key, Amounts.stringify(value));
                } else {
                  data.set(key, value);
                }
              },
            );
            return lib.exchange.uploadKycForm(reqId, data);
          },
          (res) => {
            onComplete();
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.PayloadTooLarge:
                return i18n.str`The form is too big for the server, try uploading smaller files.`;
              case HttpStatusCode.NotFound:
                return i18n.str`The account was not found`;
              case HttpStatusCode.Conflict:
                return i18n.str`Officer disabled or more recent decision was already submitted.`;
              default:
                assertUnreachable(fail);
            }
          },
        );

  return (
    <div class="rounded-lg bg-white px-5 py-6 shadow m-4">
      <LocalNotificationBanner notification={notification} />
      <div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
        {theForm.config.design.map((section, i) => {
          if (!section) return <Fragment />;
          return (
            <div
              key={i}
              class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"
            >
              <div class="px-4 sm:px-0">
                <h2 class="text-base font-semibold leading-7 text-gray-900">
                  {section.title}
                </h2>
                {section.description && (
                  <p class="mt-1 text-sm leading-6 text-gray-600">
                    {section.description}
                  </p>
                )}
              </div>
              <div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md md:col-span-2">
                <div class="p-3">
                  <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
                    <RenderAllFieldsByUiConfig
                      key={i}
                      fields={convertUiField(
                        i18n,
                        section.fields,
                        form,
                        getConverterById,
                      )}
                    />
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>

      <div class="mt-6 flex items-center justify-end gap-x-6">
        <button
          onClick={onComplete}
          class="text-sm font-semibold leading-6 text-gray-900"
        >
          <i18n.Translate>Cancel</i18n.Translate>
        </button>
        <Button
          type="submit"
          handler={submitHandler}
          // disabled={!submitHandler}
          class="disabled:opacity-50 disabled:cursor-default rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
        >
          <i18n.Translate>Confirm</i18n.Translate>
        </Button>
      </div>
    </div>
  );
}

function getRequiredFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
  const shape: Array<UIHandlerId> = [];
  fields.forEach((field) => {
    if ("id" in field) {
      // FIXME: this should be a validation when loading the form
      // consistency check
      if (shape.indexOf(field.id) !== -1) {
        throw Error(`already present: ${field.id}`);
      }
      if (!field.required) {
        return;
      }
      shape.push(field.id);
    } else if (field.type === "group") {
      Array.prototype.push.apply(shape, getRequiredFields(field.fields));
    }
  });
  return shape;
}
function getShapeFromFields(fields: UIFormElementConfig[]): Array<UIHandlerId> {
  const shape: Array<UIHandlerId> = [];
  fields.forEach((field) => {
    if ("id" in field) {
      // FIXME: this should be a validation when loading the form
      // consistency check
      if (shape.indexOf(field.id) !== -1) {
        throw Error(`already present: ${field.id}`);
      }
      shape.push(field.id);
    } else if (field.type === "group") {
      Array.prototype.push.apply(shape, getShapeFromFields(field.fields));
    }
  });
  return shape;
}
function searchForm(
  i18n: InternationalizationAPI,
  forms: FormMetadata[],
  formId: string,
  context: object | undefined,
): FormMetadata | undefined {
  {
    const found = forms.find((v) => v.id === formId);
    if (found) return found;
  }
  {
    const pf = preloadedForms(i18n, context);
    const found = pf.find((v) => v.id === formId);
    if (found) return found;
  }
  return undefined;
}
