<script lang="ts">
  import Input from '@components/input/Input.svelte';
  import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
  import Textarea from '@components/input/Textarea.svelte';
  import Password from '@components/input/Password.svelte';
  import BitBadge from '@components/input/BitBadge.svelte';
  import UserSearch from '@components/admin/search/UserSearch.svelte';
  import Checkbox from '@components/input/Checkbox.svelte';
  import type { user } from '@db/schema/user.ts';
  import Select from '@components/input/Select.svelte';

  // html bindings
  let modal: HTMLDialogElement;
  let modalForm: HTMLFormElement;

  // types
  interface Props<T> {
    texts: {
      title: string;
      submitButtonTitle: string;
      confirmPopupTitle: string;
      confirmPopupMessage: string;
    };

    target: T | null;
    keys?: Key<any>[][];

    onSubmit: (target: T) => void;
    onClose?: () => void;

    open: boolean;
  }
  type Key<T extends KeyInputType> = {
    key: string;
    type: T | null;
    label: string;
    default?: any;
    options?: KeyInputTypeOptions<T>;
  };
  type KeyInputType =
    | 'bit-badge'
    | 'checkbox'
    | 'color'
    | 'date'
    | 'datetime-local'
    | 'number'
    | 'password'
    | 'select'
    | 'tel'
    | 'text'
    | 'textarea'
    | 'user-search';
  type KeyInputTypeOptionsConvert<T extends KeyInputType> = {
    ['bit-badge']: string[];
    ['checkbox']: boolean;
    ['color']: string;
    ['date']: string;
    ['datetime-local']: string;
    ['number']: number;
    ['password']: string;
    ['select']: string;
    ['tel']: string;
    ['text']: string;
    ['textarea']: string;
    ['user-search']: typeof user.$inferSelect;
  }[T];
  type KeyInputTypeOptions<T extends KeyInputType, V = KeyInputTypeOptionsConvert<T>> = {
    ['bit-badge']: {
      available: Record<number, string>;
    };
    ['checkbox']: {};
    ['color']: {};
    ['date']: {};
    ['datetime-local']: {};
    ['number']: {};
    ['password']: {};
    ['select']: {
      options: Record<string, string>;
    };
    ['tel']: {};
    ['text']: {};
    ['textarea']: {
      rows?: boolean;
    };
    ['user-search']: {
      mustMatch?: boolean;
    };
  }[T] & {
    convert?: (value: KeyInputTypeOptionsConvert<T>) => V;
    validate?: (value: V) => boolean;
    required?: boolean;
    dynamicWidth?: boolean;
  };

  // input
  let { texts, target, keys = [], onSubmit, onClose, open = $bindable() }: Props<any> = $props();

  onInit();

  // state
  let submitEnabled = $state(false);

  $effect(() => {
    if (!open) return;

    onInit();
    modal.show();
  });

  $effect.pre(() => {
    if (target == null) onInit();
    updateSubmitEnabled();
  });

  // functions
  function onInit() {
    if (target == null) target = {};

    for (const key of keys) {
      for (const k of key) {
        if (k.default !== undefined && target[k.key] === undefined)
          target[k.key] = k.options?.convert ? k.options.convert(k.default) : k.default;
      }
    }
  }

  function updateSubmitEnabled() {
    submitEnabled = false;
    for (const key of keys) {
      for (const k of key) {
        if (k.options?.required && (!target[k.key] || (k.options.validate && !k.options.validate(target[k.key]))))
          return;
      }
    }
    submitEnabled = true;
  }

  // callbacks
  function onBindChange(key: string, value: any, options?: KeyInputTypeOptions<any>) {
    target[key] = options?.convert ? options.convert(value) : value;

    updateSubmitEnabled();
  }

  async function onSaveButtonClick(e: Event) {
    e.preventDefault();
    $confirmPopupState = {
      title: texts.confirmPopupTitle,
      message: texts.confirmPopupMessage,
      onConfirm: () => {
        modalForm.submit();
        onSubmit(target);
      }
    };
  }

  function onCancelButtonClick(e: Event) {
    e.preventDefault();
    modalForm.submit();
  }

  function onModalClose() {
    setTimeout(() => {
      open = false;
      target = null;
      modalForm.reset();
      onClose?.();
    }, 300);
  }
</script>

<dialog class="modal" bind:this={modal} onclose={onModalClose}>
  <form method="dialog" class="modal-box overflow-visible" bind:this={modalForm}>
    <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
    <div class="space-y-5">
      <h3 class="text-xl font-geist font-bold">{texts.title}</h3>
      <div class="w-full flex flex-col">
        {#each keys as key (key)}
          <div
            class="grid grid-flow-col gap-4"
            class:grid-cols-1={key.length === 1}
            class:grid-cols-2={key.length === 2}
          >
            {#each key as k (k)}
              {#if k.type === 'color' || k.type === 'date' || k.type === 'datetime-local' || k.type === 'number' || k.type === 'tel' || k.type === 'text'}
                <Input
                  type={k.type}
                  bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
                  label={k.label}
                  required={k.options?.required}
                  dynamicWidth={k.options?.dynamicWidth}
                />
              {:else if k.type === 'textarea'}
                <Textarea
                  bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
                  label={k.label}
                  required={k.options?.required}
                  rows={k.options?.rows}
                  dynamicWidth={k.options?.dynamicWidth}
                />
              {:else if k.type === 'checkbox'}
                <Checkbox
                  bind:checked={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
                  label={k.label}
                  required={k.options?.required}
                />
              {:else if k.type === 'select'}
                <Select
                  bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
                  label={k.label}
                  values={k.options.values}
                  required={k.options?.required}
                  dynamicWidth={k.options?.dynamicWidth}
                />
              {:else if k.type === 'user-search'}
                <UserSearch
                  bind:value={
                    () =>
                      target[k.key] ? (target[k.key]['username'] ?? null) : k.default ? k.default['username'] : null,
                    (v) =>
                      k.options.mustMatch
                        ? onBindChange(k.key, { id: null, username: null }, k.options)
                        : onBindChange(k.key, { id: null, username: v }, k.options)
                  }
                  onSubmit={(user) => onBindChange(k.key, user, k.options)}
                  label={k.label}
                  required={k.options?.required}
                  mustMatch={k.options?.mustMatch}
                />
              {:else if k.type === 'password'}
                <Password
                  bind:value={() => target[k.key] ?? k.default, (v) => onBindChange(k.key, v, k.options)}
                  label={k.label}
                  required={k.options?.required}
                />
              {:else if k.type === 'bit-badge'}
                <BitBadge
                  available={k.options?.available}
                  bind:value={
                    () => (target[k.key] ? (target[k.key]['name'] ?? target[k.key]) : k.default),
                    (v) => onBindChange(k.key, v, k.options)
                  }
                />
              {/if}
            {/each}
          </div>
        {/each}
      </div>
      <div>
        <button
          class="btn btn-success"
          class:disabled={!submitEnabled}
          disabled={!submitEnabled}
          onclick={onSaveButtonClick}>{texts.submitButtonTitle}</button
        >
        <button class="btn btn-error" type="button" onclick={onCancelButtonClick}>Abbrechen</button>
      </div>
    </div>
  </form>
  <form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
    <button class="!cursor-default">close</button>
  </form>
</dialog>
