import {
  createEffect,
  createMemo,
  createRenderEffect,
  createSignal,
  Show,
  type Accessor,
  type Component,
  type JSX,
  type Ref,
} from "solid-js";
import Scaffold from "../material/Scaffold";
import {
  Avatar,
  Button,
  IconButton,
  List,
  ListItemButton,
  ListItemIcon,
  ListItemSecondaryAction,
  ListItemText,
  Radio,
  Switch,
  Divider,
  CircularProgress,
  Toolbar,
  MenuItem,
  ListItemAvatar,
} from "@suid/material";
import {
  ArrowDropDown,
  Public as PublicIcon,
  Send,
  People as PeopleIcon,
  ThreeP as ThreePIcon,
  ListAlt as ListAltIcon,
  Visibility,
  Translate,
  Close,
  MoreVert,
} from "@suid/icons-material";
import type { Account } from "../accounts/stores";
import "./TootComposer.css";
import BottomSheet from "../material/BottomSheet";
import { useLanguage } from "../platform/i18n";
import iso639_1 from "iso-639-1";
import ChooseTootLang from "./ChooseTootLang";
import type { mastodon } from "masto";
import cardStyles from "../material/cards.module.css";
import Menu, { createManagedMenuState } from "../material/Menu";
import { useDefaultSession } from "../masto/clients";
import { resolveCustomEmoji } from "../masto/toot";
import SizedTextarea from "../platform/SizedTextarea";

type TootVisibility = "public" | "unlisted" | "private" | "direct";

const TootVisibilityPickerDialog: Component<{
  open?: boolean;
  class?: string;
  onClose: () => void;
  visibility: TootVisibility;
  onVisibilityChange: (value: TootVisibility) => void;
}> = (props) => {
  type Kind = "public" | "private" | "direct";

  const kind = () =>
    props.visibility === "public" || props.visibility === "unlisted"
      ? "public"
      : props.visibility;

  const setKind = (nv: Kind) => {
    if (nv == "public") {
      props.onVisibilityChange(discoverable() ? "public" : "unlisted");
    } else {
      props.onVisibilityChange(nv);
    }
  };

  const discoverable = () => {
    return props.visibility === "public";
  };

  const setDiscoverable = (setter: (v: boolean) => boolean) => {
    const nval = setter(discoverable());
    props.onVisibilityChange(nval ? "public" : "unlisted"); // trigger change
  };

  return (
    <BottomSheet
      open={props.open}
      onClose={props.onClose}
      bottomUp
      class={props.class}
    >
      <Scaffold
        bottom={
          <div
            style={{
              "border-top": "1px solid #ddd",
              background: "var(--tutu-color-surface)",
              padding: "8px 16px",
              width: "100%",
              "text-align": "end",
            }}
          >
            <Button onClick={props.onClose}>Confirm</Button>
          </div>
        }
      >
        <List dense>
          <ListItemButton onClick={[setKind, "public"]}>
            <ListItemIcon>
              <PublicIcon />
            </ListItemIcon>
            <ListItemText
              primary="Public"
              secondary="Everyone can see this toot"
            ></ListItemText>
            <ListItemSecondaryAction>
              <Radio checked={kind() == "public"}></Radio>
            </ListItemSecondaryAction>
          </ListItemButton>

          <ListItemButton
            sx={{ paddingLeft: "40px" }}
            disabled={kind() !== "public"}
            onClick={() => setDiscoverable((x) => !x)}
          >
            <ListItemIcon>
              <ListAltIcon />
            </ListItemIcon>
            <ListItemText
              primary="Discoverable"
              secondary="The others can discover it on the exploration."
            ></ListItemText>
            <ListItemSecondaryAction>
              <Switch
                checked={discoverable()}
                disabled={kind() !== "public"}
              ></Switch>
            </ListItemSecondaryAction>
          </ListItemButton>

          <Divider />

          <ListItemButton onClick={[setKind, "private"]}>
            <ListItemIcon>
              <PeopleIcon />
            </ListItemIcon>
            <ListItemText
              primary="Only Followers"
              secondary="Visibile for followers only"
            ></ListItemText>
            <ListItemSecondaryAction>
              <Radio checked={kind() == "private"}></Radio>
            </ListItemSecondaryAction>
          </ListItemButton>

          <Divider />

          <ListItemButton onClick={[setKind, "direct"]}>
            <ListItemIcon>
              <ThreePIcon />
            </ListItemIcon>
            <ListItemText
              primary="Only Mentions"
              secondary="Visible for mentioned users only"
            ></ListItemText>
            <ListItemSecondaryAction>
              <Radio checked={kind() == "direct"}></Radio>
            </ListItemSecondaryAction>
          </ListItemButton>
        </List>
      </Scaffold>
    </BottomSheet>
  );
};

const TootLanguagePickerDialog: Component<{
  open?: boolean;
  class?: string;
  onClose: () => void;
  code: string;
  onCodeChange: (nval: string) => void;
}> = (props) => {
  return (
    <BottomSheet open={props.open} onClose={props.onClose} class={props.class}>
      <Show when={props.open}>
        <ChooseTootLang
          code={props.code}
          onCodeChange={props.onCodeChange}
          onClose={props.onClose}
        />
      </Show>
    </BottomSheet>
  );
};

function randomChoose<T extends any[]>(
  rn: number,
  K: T,
): T extends Array<infer E> ? E : never {
  const idx = Math.floor(rn * K.length);
  return K[idx];
}

function useRandomChoice<T>(choices: () => T[]): Accessor<T> {
  return createMemo(() => randomChoose(Math.random(), choices()));
}

function cancelEvent(event: Event) {
  event.stopPropagation();
}

const TootComposer: Component<{
  ref?: Ref<HTMLDivElement>;
  style?: JSX.CSSProperties;
  profile?: Account;
  replyToDisplayName?: string;
  mentions?: readonly string[];
  client?: mastodon.rest.Client;
  inReplyToId?: string;
  onSent?: (status: mastodon.v1.Status) => void;
}> = (props) => {
  let inputRef: HTMLTextAreaElement;

  const session = useDefaultSession();

  const [active, setActive] = createSignal(false);
  const [sending, setSending] = createSignal(false);
  const [visibility, setVisibility] = createSignal<TootVisibility>("public");
  const [permPicker, setPermPicker] = createSignal(false);
  const [language, setLanguage] = createSignal("en");
  const [langPickerOpen, setLangPickerOpen] = createSignal(false);
  const appLanguage = useLanguage();
  const [openMenu, menuState] = createManagedMenuState();

  const randomPlaceholder = useRandomChoice(() => [
    "What's happening?",
    "What do you think?",
  ]);

  createEffect(() => {
    const lang = appLanguage().split("-")[0];
    setLanguage(lang);
  });

  createEffect(() => {
    if (active()) {
      setTimeout(() => inputRef.focus(), 0);
    }
  });

  createEffect(() => {
    if (inputRef.value !== "") return;
    if (props.mentions) {
      const prepText = props.mentions.join(" ") + " ";
      inputRef.value = prepText;
    }
  });

  const containerStyle = () =>
    active() || permPicker()
      ? {
          position: "sticky" as const,
          top: "var(--scaffold-topbar-height, 0)",
          bottom: "var(--safe-area-inset-bottom, 0)",
          "z-index": 2,
          ...props.style,
        }
      : undefined;

  const visibilityText = () => {
    switch (visibility()) {
      case "public":
        return "Discoverable";
      case "unlisted":
        return "Public";
      case "private":
        return "Only Followers";
      case "direct":
        return "Only Mentions";
    }
  };

  const idempotencyKey = createMemo(() => window.crypto.randomUUID());

  const send = async () => {
    const client = session()?.client;
    if (!client) return;

    setSending(true);
    try {
      const status = await client.v1.statuses.create(
        {
          status: inputRef.value,
          language: language(),
          visibility: visibility(),
          inReplyToId: props.inReplyToId,
        },
        {
          requestInit: {
            headers: {
              ["Idempotency-Key"]: idempotencyKey(),
            },
          },
        },
      );

      props.onSent?.(status);
      inputRef.value = "";
    } finally {
      setSending(false);
    }
  };

  return (
    <div
      ref={props.ref}
      class={/* @once */ `TootComposer ${cardStyles.card}`}
      style={containerStyle()}
      on:touchend={
        cancelEvent
        /* on: is required to register the event handler on the exact element */
      }
      on:touchmove={cancelEvent}
      on:wheel={cancelEvent}
    >
      <Show when={active()}>
        <Toolbar class={cardStyles.cardNoPad}>
          <IconButton
            onClick={[setActive, false]}
            aria-label="Close the composer"
          >
            <Close />
          </IconButton>
          <IconButton
            onClick={(e) => openMenu(e.currentTarget.getBoundingClientRect())}
          >
            <MoreVert />
          </IconButton>
        </Toolbar>
        <div class={cardStyles.cardNoPad}>
          <Menu {...menuState}>
            <MenuItem>
              <ListItemAvatar>
                <Avatar src={session()?.account.inf?.avatar}></Avatar>
              </ListItemAvatar>
              <ListItemText secondary={"Default account"}>
                <span
                  ref={(e) => {
                    createRenderEffect(() => {
                      const inf = session()?.account.inf;
                      return (e.innerHTML = resolveCustomEmoji(
                        inf?.displayName || "",
                        inf?.emojis ?? [],
                      ));
                    });
                  }}
                ></span>
              </ListItemText>
            </MenuItem>
          </Menu>
        </div>
      </Show>

      <div class="reply-input">
        <Show when={props.profile}>
          <Avatar
            src={props.profile!.inf?.avatar}
            sx={{ marginLeft: "-0.25em" }}
          />
        </Show>
        <SizedTextarea
          ref={inputRef!}
          placeholder={
            props.replyToDisplayName
              ? `Reply to ${props.replyToDisplayName}...`
              : randomPlaceholder()
          }
          onFocus={[setActive, true]}
          style={{ width: "100%", border: "none" }}
          disabled={sending()}
          autocomplete="off"
        ></SizedTextarea>
        <Show when={props.client}>
          <Show
            when={!sending()}
            fallback={
              <div style={{ padding: "8px" }}>
                <CircularProgress
                  sx={{
                    marginRight: "-0.5em",
                    width: "1.5rem",
                    height: "1.5rem",
                  }}
                />
              </div>
            }
          >
            <IconButton
              sx={{ marginRight: "-0.5em" }}
              onClick={send}
              aria-label="Send"
            >
              <Send />
            </IconButton>
          </Show>
        </Show>
      </div>

      <Show when={active()}>
        <div class="options">
          <Button
            startIcon={<Translate />}
            endIcon={<ArrowDropDown />}
            onClick={[setLangPickerOpen, true]}
            disabled={sending()}
          >
            <span style={{ "vertical-align": "bottom" }}>
              {iso639_1.getNativeName(language())}
            </span>
          </Button>
          <Button
            startIcon={<Visibility />}
            endIcon={<ArrowDropDown />}
            onClick={[setPermPicker, true]}
            disabled={sending()}
          >
            <span style={{ "vertical-align": "bottom" }}>
              {visibilityText()}
            </span>
          </Button>
        </div>

        <TootVisibilityPickerDialog
          class={cardStyles.cardNoPad}
          open={permPicker()}
          onClose={() => setPermPicker(false)}
          visibility={visibility()}
          onVisibilityChange={setVisibility}
        />

        <TootLanguagePickerDialog
          class={cardStyles.cardNoPad}
          open={langPickerOpen()}
          onClose={() => setLangPickerOpen(false)}
          code={language()}
          onCodeChange={setLanguage}
        />
      </Show>
    </div>
  );
};

export default TootComposer;