/*
 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,
  AmountJson,
  TalerExchangeApi,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  UIFieldHandler,
  UIFormElementConfig,
  UIHandlerId,
} from "@gnu-taler/web-util/browser";
import { useState } from "preact/hooks";
import { undefinedIfEmpty } from "../pages/Start.js";

// export type UIField = {
//   value: string | undefined;
//   onUpdate: (s: string) => void;
//   error: TranslatedString | undefined;
// };

export type FormHandler<T> = {
  [k in keyof T]?: T[k] extends string
    ? UIFieldHandler
    : T[k] extends AmountJson
      ? UIFieldHandler
      : T[k] extends TalerExchangeApi.AmlState
        ? UIFieldHandler
        : FormHandler<T[k]>;
};

export type FormValues<T> = {
  [k in keyof T]: T[k] extends string ? string | undefined : FormValues<T[k]>;
};

export type RecursivePartial<T> = {
  [k in keyof T]?: T[k] extends string
    ? string
    : T[k] extends AmountJson
      ? AmountJson
      : T[k] extends TalerExchangeApi.AmlState
        ? TalerExchangeApi.AmlState
        : RecursivePartial<T[k]>;
};

export type FormErrors<T> = {
  [k in keyof T]?: T[k] extends string
    ? TranslatedString
    : T[k] extends AmountJson
      ? TranslatedString
      : T[k] extends AbsoluteTime
        ? TranslatedString
        : T[k] extends TalerExchangeApi.AmlState
          ? TranslatedString
          : FormErrors<T[k]>;
};

export type FormStatus<T> =
  | {
      status: "ok";
      result: T;
      errors: undefined;
    }
  | {
      status: "fail";
      result: RecursivePartial<T>;
      errors: FormErrors<T>;
    };

function constructFormHandler<T>(
  shape: Array<UIHandlerId>,
  form: RecursivePartial<FormValues<T>>,
  updateForm: (d: RecursivePartial<FormValues<T>>) => void,
  errors: FormErrors<T> | undefined,
): FormHandler<T> {
  const handler = shape.reduce((handleForm, fieldId) => {
    const path = fieldId.split(".");

    function updater(newValue: unknown) {
      updateForm(setValueDeeper(form, path, newValue));
    }

    const currentValue = getValueDeeper<string>(form as any, path, undefined);
    const currentError = getValueDeeper<TranslatedString>(
      errors as any,
      path,
      undefined,
    );
    const field: UIFieldHandler = {
      error: currentError,
      value: currentValue,
      onChange: updater,
      state: {}, //FIXME: add the state of the field (hidden, )
    };

    return setValueDeeper(handleForm, path, field);
  }, {} as FormHandler<T>);

  return handler;
}

/**
 * FIXME: Consider sending this to web-utils
 *
 *
 * @param defaultValue
 * @param check
 * @returns
 */
export function useFormState<T>(
  shape: Array<UIHandlerId>,
  defaultValue: RecursivePartial<FormValues<T>>,
  check: (f: RecursivePartial<FormValues<T>>) => FormStatus<T>,
): [FormHandler<T>, FormStatus<T>] {
  const [form, updateForm] =
    useState<RecursivePartial<FormValues<T>>>(defaultValue);

  const status = check(form);
  const handler = constructFormHandler(shape, form, updateForm, status.errors);

  return [handler, status];
}

interface Tree<T> extends Record<string, Tree<T> | T> {}

export function getValueDeeper<T>(
  object: Tree<T> | undefined,
  names: string[],
  notFoundValue?: T,
): T | undefined {
  if (names.length === 0) return object as T;
  const [head, ...rest] = names;
  if (!head) {
    return getValueDeeper(object, rest, notFoundValue);
  }
  if (object === undefined) {
    return notFoundValue;
  }
  return getValueDeeper(object[head] as Tree<T>, rest, notFoundValue);
}

export function setValueDeeper(object: any, names: string[], value: any): any {
  if (names.length === 0) return value;
  const [head, ...rest] = names;
  if (!head) {
    return setValueDeeper(object, rest, value);
  }
  if (object === undefined) {
    return undefinedIfEmpty({ [head]: setValueDeeper({}, rest, value) });
  }
  return undefinedIfEmpty({ ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) });
}

export 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;
}

export 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;
}
export function validateRequiredFields<FormType>(
  errors: FormErrors<FormType> | undefined,
  form: object,
  fields: Array<UIHandlerId>,
): FormErrors<FormType> | undefined {
  let result: FormErrors<FormType> | undefined = errors;
  fields.forEach((f) => {
    const path = f.split(".");
    const v = getValueDeeper(form as any, path);
    result = setValueDeeper(result, path, !v ? "required" : undefined);
  });
  return result;
}