/*
 This file is part of GNU Taler
 (C) 2022-2025 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 {
  AmlProgramRequirement,
  assertUnreachable,
  AvailableMeasureSummary,
  KycCheckInformation,
  TalerError,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  design_challenger_email,
  design_challenger_phone,
  design_challenger_postal,
  FormDesign,
  FormUI,
  InternationalizationAPI,
  RecursivePartial,
  useForm,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useCurrentDecisionRequest } from "../hooks/decision-request.js";
import { useServerMeasures } from "../hooks/server-info.js";

const TALER_SCREEN_ID = 122;

export type MeasureDefinition = {
  name: string;
  program: string;
  check: string;
  context: {
    key: string;
    type: "string" | "number" | "boolean" | "json";
    value: string;
  }[];
};

type VerificationMeasureDefinition = {
  name: string;
  readOnly: boolean;
  address: object;
};

/**
 * Defined new limits for the account
 * @param param0
 * @returns
 */
export function NewMeasure({
  initial,
  isNew,
  onCancel,
  onAdded,
  onChanged,
  onRemoved,
}: {
  initial?: Partial<MeasureDefinition>;
  isNew?: boolean;
  onCancel: () => void;
  onAdded: (name: string) => void;
  onChanged: (name: string) => void;
  onRemoved: (name: string) => void;
}): VNode {
  const measures = useServerMeasures();
  const { i18n } = useTranslationContext();

  const summary =
    !measures || measures instanceof TalerError || measures.type === "fail"
      ? undefined
      : measures.body;

  if (!summary) {
    return (
      <div>
        <i18n.Translate>loading...</i18n.Translate>
      </div>
    );
  }

  return (
    <MeasureForm
      summary={summary}
      initial={initial}
      onCancel={onCancel}
      onAdded={onAdded}
      onChanged={onChanged}
      onRemoved={onRemoved}
      addingNew={isNew}
    />
  );
}

function NormalMeasureForm({
  summary,
  onCancel,
  onAdded,
  onChanged,
  onRemoved,
  initial,
  addingNew,
}: {
  initial?: Partial<MeasureDefinition>;
  addingNew?: boolean;
  summary: AvailableMeasureSummary;
  onCancel: () => void;
  onAdded: (name: string) => void;
  onChanged: (name: string) => void;
  onRemoved: (name: string) => void;
}): VNode {
  const [request, updateRequest] = useCurrentDecisionRequest();
  const { i18n } = useTranslationContext();

  const names = {
    measures: Object.entries(summary.roots).map(([key, value]) => ({
      key,
      value,
    })),
    programs: Object.entries(summary.programs).map(([key, value]) => ({
      key,
      value,
    })),
    checks: Object.entries(summary.checks).map(([key, value]) => ({
      key,
      value,
    })),
  };

  const design = formDesign(
    i18n,
    names.programs,
    names.checks,
    summary,
    !addingNew,
  );

  const form = useForm<MeasureDefinition>(design, initial ?? {});

  const name = !form.status.result ? undefined : form.status.result.name;

  function addNewCustomMeasure() {
    const newMeasure = form.status.result as MeasureDefinition;
    const currentMeasures = { ...request.custom_measures };
    currentMeasures[newMeasure.name] = {
      check_name: newMeasure.check,
      prog_name: newMeasure.program,
      context: (newMeasure.context ?? []).reduce(
        (prev, cur) => {
          prev[cur.key] = getContextValueByType(cur.type, cur.value);
          return prev;
        },
        {} as Record<string, object>,
      ),
    };
    updateRequest("add new measure", {
      custom_measures: currentMeasures,
    });
    if (onAdded) {
      onAdded(newMeasure.name);
    }
  }

  function updateCurrentCustomMeasure() {
    const newMeasure = form.status.result as MeasureDefinition;

    const CURRENT_MEASURES = { ...request.custom_measures };
    CURRENT_MEASURES[newMeasure.name] = {
      check_name: newMeasure.check,
      prog_name: newMeasure.program,
      context: (newMeasure.context ?? []).reduce(
        (prev, cur) => {
          prev[cur.key] = getContextValueByType(cur.type, cur.value);
          return prev;
        },
        {} as Record<string, object>,
      ),
    };
    updateRequest("update measure", {
      custom_measures: CURRENT_MEASURES,
    });
    if (onChanged) {
      onChanged(newMeasure.name);
    }
  }

  function removeCustomMeasure() {
    const currentMeasures = { ...request.custom_measures };
    delete currentMeasures[name!];
    updateRequest("remove measure", {
      custom_measures: currentMeasures,
    });
    if (onRemoved) {
      onRemoved(name!);
    }
  }

  return (
    <Fragment>
      <FormUI design={design} model={form.model} />

      <button
        onClick={() => {
          onCancel();
        }}
        class="m-4  rounded-md w-fit border-1 px-3 py-2 text-center text-sm shadow-sm "
      >
        <i18n.Translate>Cancel</i18n.Translate>
      </button>

      {addingNew ? (
        <button
          disabled={form.status.status === "fail"}
          onClick={addNewCustomMeasure}
          class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
        >
          <i18n.Translate>Add</i18n.Translate>
        </button>
      ) : (
        <Fragment>
          <button
            disabled={form.status.status === "fail"}
            onClick={updateCurrentCustomMeasure}
            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
          >
            <i18n.Translate>Update</i18n.Translate>
          </button>

          <button
            onClick={removeCustomMeasure}
            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
          >
            <i18n.Translate>Remove</i18n.Translate>
          </button>
        </Fragment>
      )}

      <DescribeMeasure measure={form.status.result} summary={summary} />
    </Fragment>
  );
}
function VerificationMeasureForm({
  summary,
  onCancel,
  onAdded,
  onChanged,
  onRemoved,
  initial,
  addingNew,
  challengeType,
}: {
  initial?: Partial<MeasureDefinition>;
  addingNew?: boolean;
  summary: AvailableMeasureSummary;
  onCancel: () => void;
  onAdded: (name: string) => void;
  onChanged: (name: string) => void;
  onRemoved: (name: string) => void;
  challengeType: "email" | "phone" | "postal";
}): VNode {
  const [request, updateRequest] = useCurrentDecisionRequest();
  const { i18n } = useTranslationContext();

  const design = verificationFormDesign(
    i18n,
    summary,
    !addingNew,
    challengeType,
  );

  const initAddr = (initial?.context ?? []).find(
    (d) => d.key === "initial_address",
  );

  let readOnly: boolean | undefined;
  let rest = {};
  if (initAddr && initAddr.value) {
    const va = JSON.parse(initAddr.value);
    readOnly = va.read_only;
    delete va.read_only;
    rest = { ...va };
  }

  const template: Partial<VerificationMeasureDefinition> = {
    name: initial?.name,
    readOnly,
    address: rest,
  };

  const form = useForm<VerificationMeasureDefinition>(design, template ?? {});

  // const name = !form.status.result ? undefined : form.status.result.name;

  if (!initial) {
    throw Error("verification doesn't have initial value");
  }
  if (!initial.check) {
    throw Error("verification doesn't have check");
  }
  if (!initial.program) {
    throw Error("verification doesn't have program");
  }
  if (!initial.context) {
    throw Error("verification doesn't have program");
  }
  if (!initial.name) {
    throw Error("verification doesn't have name");
  }

  const check_name = initial.check;
  const measure_name = initial.name;
  const prog_name = initial.program;
  const context = initial.context.reduce(
    (prev, cur) => {
      prev[cur.key] = getContextValueByType(cur.type, cur.value);
      return prev;
    },
    {} as Record<string, object>,
  );

  function addNewCustomMeasure() {
    const newMeasure = form.status.result as VerificationMeasureDefinition;
    const currentMeasures = { ...request.custom_measures };
    delete currentMeasures[measure_name];

    currentMeasures[newMeasure.name] = {
      check_name,
      prog_name,
      context: {
        ...context,
        initial_address: {
          read_only: newMeasure.readOnly,
          ...newMeasure.address,
        },
      },
    };
    updateRequest("add new measure", {
      custom_measures: currentMeasures,
    });
    if (onAdded) {
      onAdded(newMeasure.name);
    }
  }

  function updateCurrentCustomMeasure() {
    const newMeasure = form.status.result as VerificationMeasureDefinition;

    const CURRENT_MEASURES = { ...request.custom_measures };
    CURRENT_MEASURES[newMeasure.name] = {
      check_name,
      prog_name,
      context: {
        ...context,
        initial_address: {
          read_only: newMeasure.readOnly,
          ...newMeasure.address,
        },
      },
    };
    updateRequest("update measure", {
      custom_measures: CURRENT_MEASURES,
    });
    if (onChanged) {
      onChanged(newMeasure.name);
    }
  }

  function removeCustomMeasure() {
    const newMeasure = form.status.result as VerificationMeasureDefinition;
    const currentMeasures = { ...request.custom_measures };
    delete currentMeasures[newMeasure.name];
    updateRequest("remove measure", {
      custom_measures: currentMeasures,
    });
    if (onRemoved) {
      onRemoved(name!);
    }
  }

  return (
    <Fragment>
      <FormUI design={design} model={form.model} />

      <button
        onClick={() => {
          onCancel();
        }}
        class="m-4  rounded-md w-fit border-1 px-3 py-2 text-center text-sm shadow-sm "
      >
        <i18n.Translate>Cancel</i18n.Translate>
      </button>

      {addingNew ? (
        <button
          disabled={form.status.status === "fail"}
          onClick={addNewCustomMeasure}
          class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
        >
          <i18n.Translate>Add</i18n.Translate>
        </button>
      ) : (
        <Fragment>
          <button
            disabled={form.status.status === "fail"}
            onClick={updateCurrentCustomMeasure}
            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
          >
            <i18n.Translate>Update</i18n.Translate>
          </button>

          <button
            onClick={removeCustomMeasure}
            class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
          >
            <i18n.Translate>Remove</i18n.Translate>
          </button>
        </Fragment>
      )}

      <DescribeMeasure measure={form.status.result} summary={summary} />
    </Fragment>
  );
}

function MeasureForm({
  summary,
  onCancel,
  onAdded,
  onChanged,
  onRemoved,
  initial,
  addingNew,
}: {
  initial?: Partial<MeasureDefinition>;
  addingNew?: boolean;
  summary: AvailableMeasureSummary;
  onCancel: () => void;
  onAdded: (name: string) => void;
  onChanged: (name: string) => void;
  onRemoved: (name: string) => void;
}) {
  const challengeType = (initial?.context ?? []).find(
    (c) => c.key === "challenge-type",
  );
  const measureIsVerificationType = challengeType !== undefined;
  const [formType, setFormType] = useState<"verification" | "normal">(
    measureIsVerificationType ? "verification" : "normal",
  );

  const { i18n } = useTranslationContext();

  switch (formType) {
    case "verification": {
      const cType = !challengeType
        ? undefined
        : JSON.parse(challengeType.value);
      return (
        <div>
          <h2 class="mt-4 mb-2">
            <i18n.Translate>
              Configure verification type: {cType}
            </i18n.Translate>
          </h2>
          <div>
            <button
              onClick={async () => {
                setFormType("normal");
              }}
              class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
            >
              <i18n.Translate>Show complete form</i18n.Translate>
            </button>
          </div>

          <VerificationMeasureForm
            onAdded={onAdded}
            onCancel={onCancel}
            onChanged={onChanged}
            onRemoved={onRemoved}
            summary={summary}
            addingNew={addingNew}
            initial={initial}
            challengeType={cType}
          />
        </div>
      );
    }
    case "normal": {
      return (
        <div>
          <h2 class="mt-4 mb-2">
            <i18n.Translate>Configure measure</i18n.Translate>
          </h2>
          {measureIsVerificationType ? (
            <div>
              <button
                onClick={async () => {
                  setFormType("verification");
                }}
                class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
              >
                <i18n.Translate>Show as verification</i18n.Translate>
              </button>
            </div>
          ) : undefined}

          <NormalMeasureForm
            onAdded={onAdded}
            onCancel={onCancel}
            onChanged={onChanged}
            onRemoved={onRemoved}
            summary={summary}
            addingNew={addingNew}
            initial={initial}
          />
        </div>
      );
    }
    default: {
      assertUnreachable(formType);
    }
  }
}

const formDesign = (
  i18n: InternationalizationAPI,
  programs: { key: string; value: AmlProgramRequirement }[],
  checks: { key: string; value: KycCheckInformation }[],
  summary: AvailableMeasureSummary,
  cantChangeName: boolean,
): FormDesign => ({
  type: "single-column",
  fields: [
    {
      id: "name",
      type: "text",
      required: true,
      disabled: cantChangeName,
      label: i18n.str`Name`,
      validator(value) {
        return !value
          ? i18n.str`Required`
          : summary.roots[value]
            ? i18n.str`There is already a measure with that name`
            : undefined;
      },
    },
    {
      type: "selectOne",
      id: "program",
      label: i18n.str`Program`,
      choices: programs.map((m) => {
        return {
          value: m.key,
          label: m.key,
        };
      }),
      help: i18n.str`Only required when no check is specified`,
      validator(value, form) {
        return !value
          ? !form.check
            ? i18n.str`Missing check or program`
            : undefined
          : programAndCheckMatch(i18n, summary, value, form.check) ??
              programAndContextMatch(i18n, summary, value, form.context);
      },
    },
    {
      type: "selectOne",
      id: "check",
      label: i18n.str`Check`,
      help: i18n.str`Without a check the program will run automatically`,
      choices: checks.map((m) => {
        return {
          value: m.key,
          label: m.key,
        };
      }),
      validator(value, form) {
        return checkAndcontextMatch(
          i18n,
          summary,
          value,
          (form.context ?? []) as {
            key: string;
            value: string;
          }[],
        );
      },
    },
    {
      type: "array",
      id: "context",
      label: i18n.str`Context`,
      labelFieldId: "key",
      fields: [
        {
          type: "text",
          id: "key",
          required: true,
          label: i18n.str`Field name`,
        },
        {
          type: "choiceHorizontal",
          id: "type",
          label: i18n.str`Type`,
          required: true,
          choices: [
            {
              label: i18n.str`string`,
              value: "string",
            },
            {
              label: i18n.str`number`,
              value: "number",
            },
            {
              label: i18n.str`boolean`,
              value: "boolean",
            },
            {
              label: i18n.str`json`,
              value: "json",
            },
          ],
        },
        {
          type: "textArea",
          id: "value",
          required: true,
          label: i18n.str`Value`,
          validator(value, form) {
            return validateContextValueByType(i18n, form["type"], value);
          },
        },
      ],
    },
  ],
});

function programAndCheckMatch(
  i18n: InternationalizationAPI,
  summary: AvailableMeasureSummary,
  progName: string,
  checkName: string | undefined,
): TranslatedString | undefined {
  const program = summary.programs[progName];
  if (checkName === undefined) {
    if (program.inputs.length > 0) {
      return i18n.str`There are unsatisfied inputs: ${program.inputs.join(
        ", ",
      )}`;
    }
    return undefined;
  }
  const check = summary.checks[checkName];
  const missing = program.inputs.filter((d) => {
    return check.outputs.indexOf(d) === -1;
  });
  if (missing.length > 0) {
    return i18n.str`There are missing inputs: ${missing.join(", ")}`;
  }
  return;
}

function checkAndcontextMatch(
  i18n: InternationalizationAPI,
  summary: AvailableMeasureSummary,
  checkName: string | undefined,
  context: { key: string; value: string }[] | undefined,
): TranslatedString | undefined {
  if (checkName === undefined) {
    return undefined;
  }
  const check = summary.checks[checkName];
  const output = !context ? [] : context.map((d) => d.key);
  const missing = check.requires.filter((d) => {
    return output.indexOf(d) === -1;
  });
  if (missing.length > 0) {
    return i18n.str`There are missing requirements: ${missing.join(", ")}`;
  }
  return;
}

function programAndContextMatch(
  i18n: InternationalizationAPI,
  summary: AvailableMeasureSummary,
  program: string,
  context: { key: string; value: string }[] | undefined,
): TranslatedString | undefined {
  const check = summary.programs[program];
  const output = !context ? [] : context.map((d) => d.key);
  const missing = check.context.filter((d) => {
    return output.indexOf(d) === -1;
  });
  if (missing.length > 0) {
    return i18n.str`There are missing requirements: ${missing.join(", ")}`;
  }
  return;
}

function getJsonError(str: string) {
  try {
    JSON.parse(str);
    return undefined;
  } catch (e) {
    if (e instanceof SyntaxError) {
      return e.message;
    }
    return String(e);
  }
}

// convert the string value of the form into the corresponding type
// based on the user choice
// check the function validateContextValueByType
function getContextValueByType(type: string, value: string) {
  if (type === "number") {
    return Number.parseInt(value, 10);
  }
  if (type === "boolean") {
    return value === "true" ? true : value === "false" ? false : undefined;
  }
  if (type === "json") {
    return JSON.parse(value);
  }
  return value;
}

const REGEX_NUMER = /^[0-9]*$/;

function validateContextValueByType(
  i18n: InternationalizationAPI,
  type: string,
  value: string,
) {
  if (!value) return i18n.str`Can't be empty`;
  if (type === "number") {
    const num = Number.parseInt(value, 10);
    return !REGEX_NUMER.test(value)
      ? i18n.str`It should be a number`
      : Number.isNaN(num)
        ? i18n.str`Not a number`
        : !Number.isFinite(num)
          ? i18n.str`It should be finite`
          : !Number.isSafeInteger(num)
            ? i18n.str`It should be a safe integer`
            : undefined;
  }
  if (type === "boolean") {
    if (value === "true" || value === "false") return undefined;
    return i18n.str`It should be either "true" or "false"`;
  }
  if (type === "json") {
    const error = getJsonError(value);
    if (error) {
      return i18n.str`Couldn't parse as json string: ${error}`;
    }
    return undefined;
  }
  return undefined;
}

function DescribeProgram({
  name,
  program,
}: {
  name: string;
  program: AmlProgramRequirement;
}): VNode {
  const { i18n } = useTranslationContext();
  return (
    <div class="rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 border ring-gray-900/5 ">
      <dl class="flex flex-wrap">
        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
          <dt class="text-sm/6  text-white">
            <i18n.Translate>Program</i18n.Translate>
          </dt>
          <dd class="mt-1 text-base font-semibold text-white">{name}</dd>
        </div>
        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Description</i18n.Translate>
          </dt>
          <dd class="text-sm/6 ">
            <i18n.Translate>{program.description}</i18n.Translate>
          </dd>
        </div>
        <div class="mt-2 flex w-full flex-none gap-x-4 border-t border-gray-900/5 px-6 pt-2">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Context</i18n.Translate>
          </dt>
          <dd class="text-sm/6 font-medium text-gray-900">
            <pre>{program.context.join(",")}</pre>
          </dd>
        </div>
        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Inputs</i18n.Translate>
          </dt>
          <dd class="text-sm/6 ">
            <pre class="whitespace-pre-wrap">{program.inputs.join(",")}</pre>
          </dd>
        </div>
      </dl>
      <div class="px-4 pb-2"></div>
    </div>
  );
}
function DescribeCheck({
  name,
  check,
}: {
  name: string;
  check: KycCheckInformation;
}): VNode {
  const { i18n } = useTranslationContext();
  return (
    <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 border ring-gray-900/5 ">
      <dl class="flex flex-wrap">
        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
          <dt class="text-sm/6  text-white">
            <i18n.Translate>Check</i18n.Translate>
          </dt>
          <dd class="mt-1 text-base font-semibold text-white">{name}</dd>
        </div>
        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
          <dt class="flex-none text-gray-500">Description</dt>
          <dd class="text-sm/6 ">
            <i18n.Translate>{check.description}</i18n.Translate>
          </dd>
        </div>
        <div class="mt-2 flex w-full flex-none gap-x-4 border-t border-gray-900/5 px-6 pt-2">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Output</i18n.Translate>
          </dt>
          <dd class="text-sm/6 font-medium ">
            <pre class="whitespace-break-spaces">
              {check.outputs.join(", ")}
            </pre>
          </dd>
        </div>
        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Requires</i18n.Translate>
          </dt>
          <dd class="text-sm/6 ">
            <pre>{check.requires.join(",")}</pre>
          </dd>
        </div>
        <div class="mt-4 flex w-full flex-none gap-x-4 px-6">
          <dt class="flex-none text-gray-500">
            <i18n.Translate>Fallback</i18n.Translate>
          </dt>
          <dd class="text-sm/6 ">
            <pre>{check.fallback}</pre>
          </dd>
        </div>
      </dl>
      <div class="px-4 pb-2"></div>
    </div>
  );
}
function DescribeContext({
  context,
}: {
  context: {
    key: string;
    type: "string" | "number" | "boolean" | "json";
    value: string;
  }[];
}): VNode {
  const { i18n } = useTranslationContext();
  return (
    <div class="mt-6 rounded-lg bg-gray-150 ring-1 shadow-lg border-indigo-700 border ring-gray-900/5 ">
      <dl class="flex flex-wrap">
        <div class="flex-auto pt-4 pl-4 bg-indigo-600 rounded-t-lg">
          <dt class="text-sm/6  text-white">
            <i18n.Translate>Context</i18n.Translate>
          </dt>
          <dd class="mt-1 text-base font-semibold text-white"></dd>
        </div>
        {context.map(({ key, value }) => {
          return (
            <div key={key} class="mt-4 flex w-full flex-none gap-x-4 px-6">
              <dt class="flex-none text-gray-500">{key}</dt>
              <dd class="text-sm/6 ">
                <i18n.Translate>{value}</i18n.Translate>
              </dd>
            </div>
          );
        })}
      </dl>
      <div class="px-4 pb-2"></div>
    </div>
  );
}
function DescribeMeasure({
  measure,
  summary,
}: {
  measure: RecursivePartial<MeasureDefinition>;
  summary: AvailableMeasureSummary;
}): VNode {
  const { i18n } = useTranslationContext();
  const programName: string | undefined = measure.program;
  const program: AmlProgramRequirement | undefined =
    !programName || !summary.programs[programName]
      ? undefined
      : summary.programs[programName];

  const checkName: string | undefined = measure.check;
  const check =
    !checkName || !summary.checks[checkName]
      ? undefined
      : summary.checks[checkName];

  const context =
    !measure || !measure.context
      ? []
      : (measure.context as MeasureDefinition["context"]);

  return (
    <Fragment>
      <h2 class="mt-4 mb-2">
        <i18n.Translate>Description</i18n.Translate>
      </h2>

      {!program || !programName ? undefined : (
        <DescribeProgram name={programName} program={program} />
      )}
      {!check || !checkName ? undefined : (
        <DescribeCheck name={checkName} check={check} />
      )}
      {!context || !context.length ? undefined : (
        <DescribeContext context={context} />
      )}
    </Fragment>
  );
}

const verificationFormDesign = (
  i18n: InternationalizationAPI,
  summary: AvailableMeasureSummary,
  cantChangeName: boolean,
  challengeType: "email" | "phone" | "postal",
): FormDesign => {
  const em =
    challengeType === "email"
      ? design_challenger_email(i18n)
      : challengeType === "phone"
        ? design_challenger_phone(i18n)
        : challengeType === "postal"
          ? design_challenger_postal(i18n)
          : undefined;

  if (!em) {
    throw Error(`unknown challenge type ${challengeType} `);
  }

  const fields = em.fields.map((f) => {
    f.disabled = false;
    f.required = false;
    if ("id" in f) {
      f.id = `address.${f.id}`;
    }
    return f;
  });

  return {
    type: "single-column",
    fields: [
      {
        id: "name",
        type: "text",
        required: true,
        disabled: cantChangeName,
        label: i18n.str`Name`,
        help: i18n.str`Name of the verification measure`,
        validator(value) {
          return !value
            ? i18n.str`Required`
            : summary.roots[value]
              ? i18n.str`There is already a measure with that name`
              : undefined;
        },
      },
      {
        type: "toggle",
        id: "readOnly",
        label: i18n.str`Read only`,
        help: i18n.str`Prevent the customer of changing the address`,
      },
      ...fields,
    ],
  };
};
