import "./_env.ts";

import * as oauth from "@atcute/oauth-browser-client";

import {
  CompositeDidDocumentResolver,
  CompositeHandleResolver,
  DohJsonHandleResolver,
  LocalActorResolver,
  PlcDidDocumentResolver,
  WebDidDocumentResolver,
  WellKnownHandleResolver,
} from "@atcute/identity-resolver";

// import type { Infer as InferLexicon } from "@char/lexicon.ts";
// import type { ATProtoUniverse } from "@char/lexicon.ts/atproto";
// type UploadBlob = InferLexicon<ATProtoUniverse, "com.atproto.repo.uploadBlob">;
type UploadBlob = any;

const didDocumentResolver = new CompositeDidDocumentResolver({
  methods: {
    plc: new PlcDidDocumentResolver(),
    web: new WebDidDocumentResolver(),
  },
});

oauth.configureOAuth({
  metadata: {
    client_id:
      import.meta.env.OAUTH_CLIENT_ID ??
      "https://video.cerulea.blue/upload/client-metadata.json",
    redirect_uri: import.meta.env.OAUTH_REDIRECT_URI ?? "https://video.cerulea.blue/upload/",
  },
  identityResolver: new LocalActorResolver({
    handleResolver: new CompositeHandleResolver({
      methods: {
        dns: new DohJsonHandleResolver({
          dohUrl: "https://mozilla.cloudflare-dns.com/dns-query",
        }),
        http: new WellKnownHandleResolver(),
      },
    }),
    didDocumentResolver,
  }),
});

type Did = `did:${"web" | "plc"}:${string}`;
type ActorIdentifier = Did | `${string}.${string}`;
const login = async (identifier: ActorIdentifier) => {
  const authUrl = await oauth.createAuthorizationUrl({
    target: { type: "account", identifier: identifier },
    scope: "atproto transition:generic",
  });
  await new Promise(resolve => setTimeout(resolve, 200));
  globalThis.location.assign(authUrl);
};

const loginForm = (): HTMLFormElement => {
  const handleField = (
    <input
      type="text"
      name="handle"
      title="Enter a valid handle or DID"
      placeholder="my-handle.example.com"
      pattern="(did:.*)|(.*\..*)"
      required
    />
  ) as HTMLInputElement;

  const form = (
    <form>
      {handleField}
      <button type="submit">Login</button>
    </form>
  ) as HTMLFormElement;

  form.addEventListener("submit", e => {
    e.preventDefault();
    if (handleField.value) {
      login(handleField.value as ActorIdentifier);
    }
  });

  return form;
};

const auth = async (): Promise<oauth.OAuthUserAgent | undefined> => {
  for (const sessionId of oauth.listStoredSessions()) {
    try {
      const session = await oauth.getSession(sessionId);
      const agent = new oauth.OAuthUserAgent(session);
      return agent;
    } catch {
      continue;
    }
  }

  const params = new URLSearchParams(globalThis.location.hash.slice(1));
  if (params.has("state") && params.has("code")) {
    history.replaceState(null, "", location.pathname + location.search);
    const { session } = await oauth.finalizeAuthorization(params);
    const agent = new oauth.OAuthUserAgent(session);
    return agent;
  }

  return undefined;
};

const uploadForm = (agent: oauth.OAuthUserAgent): HTMLFormElement => {
  const uploadField = (<input type="file" accept="video/*" required />) as HTMLInputElement;
  const preview = (<video controls />) as HTMLVideoElement;
  preview.volume = 0.66;
  const uploadButton = (<button type="submit">upload</button>) as HTMLButtonElement;

  const slugField = (
    <input
      type="text"
      name="slug"
      placeholder="slug (optional)"
      title="Optional: Custom identifier for your video"
    />
  ) as HTMLInputElement;

  const titleField = (
    <input
      type="text"
      name="title"
      placeholder="title (optional)"
      title="Optional: Title for your video"
    />
  ) as HTMLInputElement;

  const descriptionField = (
    <textarea
      name="description"
      placeholder="description (optional)"
      rows={4}
      title="Optional: Description of your video"
    />
  ) as HTMLTextAreaElement;

  const uploadStatus = <span style={{ display: "none" }}>Uploading…</span>;

  uploadField.addEventListener("change", e => {
    const target = e.target as HTMLInputElement;
    if (target.files && target.files[0]) {
      const file = target.files[0];
      preview.src = URL.createObjectURL(file);
    }
  });

  const uploadWidget = (
    <div>
      {uploadButton}
      {uploadStatus}
    </div>
  );

  const form = (
    <form>
      {uploadField}
      {slugField}
      {titleField}
      {descriptionField}
      <div id="player">{preview}</div>
      {uploadWidget}
    </form>
  ) as HTMLFormElement;

  form.addEventListener("submit", async e => {
    e.preventDefault();

    const file = uploadField.files?.[0];
    if (!file) return;

    uploadButton.style.display = "none";
    uploadStatus.style.display = "inline";

    const uploadBlobRes: UploadBlob["output"] = await agent
      .handle("/xrpc/com.atproto.repo.uploadBlob", {
        method: "POST",
        headers: { "content-type": file.type },
        body: file,
      })
      .then(r => r.json());
    const blobRef = uploadBlobRes.blob;
    const cid = "$type" in blobRef ? blobRef.ref.$link : blobRef.cid;
    console.log({ cid });

    uploadStatus.textContent = "Writing record…";

    const putRecordRes = await agent
      .handle("/xrpc/com.atproto.repo.createRecord", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({
          repo: agent.sub,
          collection: "blue.cerulea.video.video",
          rkey: slugField.value || undefined,
          record: {
            $type: "blue.cerulea.video.video",
            title: titleField.value || undefined,
            description: descriptionField.value || undefined,
            video: blobRef,
          },
          validate: false,
        }),
      })
      .then(r => r.json());

    const [repo, _collection, rkey] = putRecordRes.uri.substring("at://".length).split("/");
    globalThis.location.replace(`/${repo}/video/${rkey}`);
  });

  return form;
};

const resolveHandle = async (did: string) => {
  try {
    const doc = await didDocumentResolver.resolve(did as Did);
    return (
      doc.alsoKnownAs?.find(it => it.startsWith("at://"))?.substring("at://".length) ?? did
    );
  } catch {
    return did;
  }
};

const main = async () => {
  const mainElem = document.querySelector("main")!;

  const agent = await auth();
  if (!agent) {
    mainElem.appendChild(loginForm());
    return;
  }

  const handle = await resolveHandle(agent.session.info.sub);
  mainElem.appendChild(
    <div
      style={{
        display: "flex",
        gap: "1em",
        alignItems: "baseline",
        marginBottom: "1rem",
        justifyContent: "space-around",
      }}
    >
      <p>
        signed in as <strong>{handle}</strong>
      </p>
      <button
        type="button"
        _onclick={() => {
          oauth.listStoredSessions().forEach(oauth.deleteStoredSession);
          globalThis.location.reload();
        }}
      >
        sign out
      </button>
    </div>,
  );
  mainElem.appendChild(uploadForm(agent));
};

main();
