import { formatISO9075, fromUnixTime, getUnixTime } from "date-fns";
import {
  Component,
  Show,
  mergeProps,
  splitProps,
  FlowComponent,
  createSignal,
  createEffect,
  For,
} from "solid-js";
import { validateInput } from "~/hooks/validation";
import AsteriskIcon from "~icons/ph/asterisk-bold";
import MaximizeIcon from "~icons/carbon/maximize";
import MinimizeIcon from "~icons/carbon/minimize";
import autosize from "autosize";
import type { JSX } from "solid-js";
import {
  createNativeInputValue,
  createOptionalNumberInputHandler,
} from "~/util";

export const TextInput: Component<
  {
    label: string;
    placeholder?: string;
    labelMinWidth?: string;
    suffix?: string | JSX.Element;
    size?: string;
    vertical?: boolean;
    invalidate?: (v: any) => string | boolean;
  } & JSX.InputHTMLAttributes<HTMLInputElement>
> = (p) => {
  p = mergeProps(
    {
      placeholder: p.label,
      labelMinWidth: "95px",
      size: "sm",
      vertical: false,
    },
    p
  );
  const [props, rest] = splitProps(p, [
    "name",
    "size",
    "class",
    "label",
    "placeholder",
    "suffix",
    "vertical",
    "labelMinWidth",
    "invalidate",
  ]);
  const sizes: Record<string, string> = {
    xs: "input-xs",
    sm: "input-sm",
    lg: "input-lg",
  };

  const [validate, vState] = validateInput({
    invalidate: props.invalidate,
    value: () => rest.value,
  });

  return (
    <div class="shrink form-control">
      <label
        classList={{
          "input-group": true,
          "input-group-vertical": props.vertical,
        }}
      >
        <span
          style={{
            "min-width":
              !props.vertical && props.labelMinWidth
                ? props.labelMinWidth
                : undefined,
          }}
          classList={{
            "gap-1": true,
            "h-8": props.vertical,
            "font-bold": rest.required,
            "bg-slate-200/70": vState().valid,
            "bg-red-100": !vState().valid,
          }}
        >
          {props.label}
          <Show when={rest.required}>
            <AsteriskIcon class="text-primary text-[0.5rem]" />
          </Show>
        </span>
        <input
          name={props.name || props.label}
          use:validate
          classList={{
            "peer flex-1 input invalid:input-error input-bordered": true,
            "w-0": !props.vertical,
            [(props.size && sizes[props.size]) || ""]: true,
            [props.class || ""]: true,
          }}
          type="text"
          placeholder={props.placeholder}
          {...rest}
        />
        <Show when={props.suffix}>
          <span
            classList={{
              "bg-slate-200/70": vState().valid,
              "bg-red-100": !vState().valid,
            }}
          >
            {props.suffix}
          </span>
        </Show>
      </label>
    </div>
  );
};

export const Checkbox: FlowComponent<
  JSX.InputHTMLAttributes<HTMLInputElement>
> = (p) => {
  const [props, rest] = splitProps(p, ["children"]);

  return (
    <div class="form-control">
      <label class="label py-0 cursor-pointer">
        <span class="label-text">{props.children}</span>
        <input type="checkbox" class="checkbox" {...rest} />
      </label>
    </div>
  );
};

export const TextArea: Component<
  {
    label: string;
    labelSuffixJsx?: JSX.Element;
    value?: string;
    placeholder?: string;
  } & JSX.TextareaHTMLAttributes<HTMLTextAreaElement>
> = (p) => {
  p = mergeProps({ rows: 3, placeholder: p.label }, p);
  const [props, rest] = splitProps(p, ["label", "labelSuffixJsx", "value"]);
  const [autosizeEnabled, setAutosize] = createSignal(false);
  let textareaEl: HTMLTextAreaElement = undefined!;
  createEffect(function () {
    if (autosizeEnabled()) {
      autosize(textareaEl);
    } else {
      autosize.destroy(textareaEl);
    }
  });

  return (
    <div class="form-control relative">
      <button
        type="button"
        onClick={() => setAutosize(!autosizeEnabled())}
        class="absolute right-3 top-2 hover:text-accent transition-all hover:scale-110"
      >
        <Show when={!autosizeEnabled()} fallback={<MinimizeIcon />}>
          <MaximizeIcon />
        </Show>
      </button>
      <label class="input-group input-group-vertical">
        <span class="h-8 bg-slate-200/70 flex gap-2 justify-between pr-14">
          {props.label}
          {props.labelSuffixJsx}
        </span>
        <textarea
          autocomplete="off"
          ref={textareaEl}
          value={props.value || ""}
          classList={{
            "textarea h-auto py-2 textarea-bordered leading-normal": true,
            "min-h-[150px]": autosizeEnabled(),
          }}
          {...rest}
        />
      </label>
    </div>
  );
};

export const UnixDateInput: Component<
  {
    required?: boolean;
    label: string;
    value?: number;
    onInput: (v: number) => void;
  } & Parameters<typeof TextInput>[0]
> = (p) => {
  const [props, rest] = splitProps(p, [
    "required",
    "label",
    "value",
    "onInput",
  ]);
  return (
    <TextInput
      required={props.required}
      type="date"
      label={props.label}
      max="9999-12-31"
      value={
        props.value != null
          ? formatISO9075(fromUnixTime(props.value), {
              representation: "date",
            })
          : (null as any)
      }
      onInput={(evt) => {
        if (!evt.currentTarget.valueAsDate) {
          return;
        }

        if (evt.currentTarget.valueAsDate.getFullYear() > 9999) {
          return;
        }

        props.onInput(getUnixTime(evt.currentTarget.valueAsDate));
      }}
      {...rest}
    />
  );
};

export const NumberInput: Component<
  { value?: number; onInput: (v: number | undefined) => void } & Omit<
    Parameters<typeof TextInput>[0],
    "onInput"
  >
> = (p) => {
  let el: HTMLInputElement = undefined!;
  const [props, rest] = splitProps(p, ["value", "onInput"]);
  const value = createNativeInputValue(
    () => el,
    () => props.value
  );

  return (
    <TextInput
      ref={el}
      value={value()}
      maxLength={9}
      onInput={createOptionalNumberInputHandler(props.onInput)}
      {...rest}
    />
  );
};

// TODO: Move input-group into separate component

export const Select: Component<
  {
    value?: string | number;
    options: [undefined | string | number, string][];
    label: string;
    labelMinWidth?: string;
    onChange: (v: any) => void;
  } & JSX.InputHTMLAttributes<HTMLSelectElement>
> = (p) => {
  p = mergeProps(
    {
      labelMinWidth: "95px",
    },
    p
  );
  const [props, rest] = splitProps(p, [
    "value",
    "options",
    "label",
    "labelMinWidth",
    "onChange",
  ]);

  return (
    <div class="shrink form-control">
      <label class="input-group">
        <span
          class="bg-slate-200/70"
          style={{ "min-width": props.labelMinWidth }}
        >
          {props.label}
        </span>
        <select
          class="flex-1 select select-sm select-bordered w-full overflow-hidden text-ellipsis"
          onChange={(e) => props.onChange(e.currentTarget.value)}
          {...rest}
        >
          <For each={props.options}>
            {([type, label]) => (
              <option selected={type === props.value} value={type}>
                {label}
              </option>
            )}
          </For>
        </select>
      </label>
    </div>
  );
};
