/*
 This file is part of GNU Taler
 (C) 2022 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 {
  amountFractionalBase,
  amountFractionalLength,
  AmountJson,
  amountMaxValue,
  Amounts,
  Result,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { AmountFieldHandler } from "../mui/handlers.js";
import { TextField } from "../mui/TextField.js";

const HIGH_DENOM_SYMBOL = ["", "K", "M", "G", "T", "P"];
const LOW_DENOM_SYMBOL = ["", "m", "mm", "n", "p", "f"];

/**
 * Show normalized value based on the currency unit
 */
export function AmountField({
  label,
  handler,
  lowestDenom = 1,
  highestDenom = 1,
  required,
}: {
  label: TranslatedString;
  lowestDenom?: number;
  highestDenom?: number;
  required?: boolean;
  handler: AmountFieldHandler;
}): VNode {
  const { i18n } = useTranslationContext();
  const [unit, setUnit] = useState(1);
  const [error, setError] = useState<string>("");

  const normal = normalize(handler.value, unit);
  const previousValue = Amounts.stringifyValue(normal);

  const [textValue, setTextValue] = useState<string>(previousValue);

  function updateUnit(newUnit: number) {
    setUnit(newUnit);
    const newNorm = normalize(handler.value, newUnit);
    setTextValue(Amounts.stringifyValue(newNorm));
  }

  const currency = handler.value.currency;

  const currencyLabels = buildLabelsForCurrency(
    currency,
    lowestDenom,
    highestDenom,
  );

  function positiveAmount(value: string): string {
    if (!value) {
      if (handler.onInput) {
        handler.onInput(Amounts.zeroOfCurrency(currency));
      }
    } else
      try {
        const parsed = Amounts.parseOrThrow(`${currency}:${value.trim()}`);

        const realValue = denormalize(parsed, unit);

        if (handler.onInput) {
          handler.onInput(realValue);
        }
        setError("");
      } catch (e) {
        setError(i18n.str`Amount is not valid`);
      }
    setTextValue(value);
    return value;
  }

  return (
    <Fragment>
      <TextField
        label={label}
        type="text"
        min="0"
        inputmode="decimal"
        step="0.1"
        variant="filled"
        error={handler.error}
        required={required}
        startAdornment={
          currencyLabels.length === 1 ? (
            <div
              style={{
                marginTop: 20,
                padding: "5px 12px 8px 12px",
              }}
            >
              {currency}
            </div>
          ) : (
            <select
              disabled={!handler.onInput}
              onChange={(e) => {
                const unit = Number.parseFloat(e.currentTarget.value);
                updateUnit(unit);
              }}
              value={String(unit)}
              style={{
                marginTop: 20,
                padding: "5px 12px 8px 12px",
                background: "transparent",
                border: 0,
              }}
            >
              {currencyLabels.map((c) => (
                <option key={c} value={c.unit}>
                  <div>{c.name}</div>
                </option>
              ))}
            </select>
          )
        }
        value={textValue}
        disabled={!handler.onInput}
        onInput={positiveAmount}
      />
      {error && <div style={{ color: "red" }}>{error}</div>}
    </Fragment>
  );
}

/**
 * Return the real value of a normalized unit
 * If the value is 20 and the unit is kilo == 1000 the returned value will be amount * 1000
 * @param amount
 * @param unit
 * @returns
 */
function denormalize(amount: AmountJson, unit: number): AmountJson {
  if (unit === 1 || Amounts.isZero(amount)) return amount;
  const result =
    unit < 1
      ? Amounts.divide(amount, 1 / unit)
      : Amounts.mult(amount, unit).amount;
  return result;
}

/**
 * Return the amount in the current unit.
 * If the value is 20000 and the unit is kilo == 1000 and the returned value will be amount / unit
 *
 * @param amount
 * @param unit
 * @returns
 */
function normalize(amount: AmountJson, unit: number): AmountJson {
  if (unit === 1 || Amounts.isZero(amount)) return amount;
  const result =
    unit < 1
      ? Amounts.mult(amount, 1 / unit).amount
      : Amounts.divide(amount, unit);
  return result;
}

/**
 * Take every label in HIGH_DENOM_SYMBOL and LOW_DENOM_SYMBOL and create
 * which create the corresponding unit multiplier
 * @param currency
 * @param lowestDenom
 * @param highestDenom
 * @returns
 */
function buildLabelsForCurrency(
  currency: string,
  lowestDenom: number,
  highestDenom: number,
): Array<{ name: string; unit: number }> {
  let hd = Math.floor(Math.log10(highestDenom || 1) / 3);
  let ld = Math.ceil((-1 * Math.log10(lowestDenom || 1)) / 3);

  const result: Array<{ name: string; unit: number }> = [
    {
      name: currency,
      unit: 1,
    },
  ];

  while (hd > 0) {
    result.push({
      name: `${HIGH_DENOM_SYMBOL[hd]}${currency}`,
      unit: Math.pow(10, hd * 3),
    });
    hd--;
  }
  while (ld > 0) {
    result.push({
      name: `${LOW_DENOM_SYMBOL[ld]}${currency}`,
      unit: Math.pow(10, -1 * ld * 3),
    });
    ld--;
  }
  return result;
}
