"use client";

import {
  Dispatch,
  Fragment,
  MouseEvent,
  SetStateAction,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import useSWR, { Fetcher } from "swr";
import generalMIDI from "./generalMIDI";
import getHighlights, { getFileExamples } from "./getHighlights";
import { HighlightSpec, Note, ParsedMIDIFile } from "./types";
import getKey from './getKey';
import { test } from 'node:test';
import TaggingUI from './TaggingUI';
import Synthesizer from './Synthesizer';
import NoteLane, { PlayingSpec } from './NoteLane';
declare var fetch: any;
declare var window: any;

const SERVER_URL = "http://localhost:3012";

// 1. list files
// 2. get first file
// 3. display notes

const fileListFetcher: Fetcher<string[]> = () =>
  fetch(`${SERVER_URL}/midi?select=id&order=rand_order`)
    .then((res: any) => res.json())
    .then((res: any) => res.map((x: any) => x.id));

const fileFetcher: Fetcher<ParsedMIDIFile, string> = (file: string) =>
  fetch(`${SERVER_URL}/midi?id=eq.${file}`)
    .then((res: any) => res.json())
    .then((res: any) => res[0]);

const MIDIDisplay = ({
  file,
  synthesizer,
}: {
  file: string;
  synthesizer: Synthesizer;
}) => {
  const { data: parsedFile, error } = useSWR(file, fileFetcher);

  const end = useMemo(() => {
    let result = 0;
    parsedFile?.channels.forEach((c) => {
      c.instruments.forEach((i) => {
        i.notes.forEach((n) => {
          if (n.offBeat > result) result = n.offBeat;
        });
      });
    });
    return result;
  }, [parsedFile]);

  const highlights = useMemo(() => {
    if (!parsedFile) return [];
    return getHighlights(parsedFile);
  }, [parsedFile]);

  const [playing, setPlaying] = useState<PlayingSpec>({
    channel: 0,
    instrument: 0,
    position: 0,
    end: 0,
  });

  useEffect(() => {
    if (!parsedFile) {
      return;
    }
    synthesizer.setNotes(
      parsedFile.channels[playing.channel].instruments[playing.instrument].notes
    );
    synthesizer.setTempo(parsedFile.tempo);
    synthesizer.setPosition(playing.position);
    synthesizer.play();
  }, [playing]);

  useEffect(() => {
    const handleKeyDown = async (e: KeyboardEvent) => {
      if (e.key === "5") {
        const auditionOffset = Number(
          localStorage.getItem("audition-offset") || 0
        );
        localStorage.setItem("audition-offset", String(auditionOffset + 1));
        const result = await fetch(
          `${SERVER_URL}/examples?select=example,rejection_reason&order=rand_order&offset=${auditionOffset}&limit=1&rejection_reason=is.null`
        )
          .then((res: any) => res.json())
          .then(
            (res: any) =>
              res[0] as { example: Note[]; rejection_reason: string | null }
          );
        console.log(result);
        synthesizer.stop();
        synthesizer.setNotes(result.example);
        synthesizer.setTempo(120);
        synthesizer.setPosition(0);
        synthesizer.play();
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  });

  if (!parsedFile) return <div />;

  const weights = getKey(parsedFile)[0].weights;
  let bestKey = ['null','null'];
  let bestKeyScore = -Infinity
  Object.keys(weights).forEach((quality) => {
    Object.keys((weights as any)[quality]).forEach((root) => {
      const score = (weights as any)[quality][root];
      if (score > bestKeyScore) {
        bestKeyScore = score;
        bestKey = [
          ( {0: 'C', 1: 'C#', 2: 'D', 3: 'D#', 4: 'E', 5: 'F', 6: 'F#', 7: 'G', 8: 'G#', 9: 'A', 10: 'A#', 11: 'B'} as any)[root],
          quality,
        ];
      }
    });
  });
  console.log(parsedFile);
  return (
    <div className="grow w-full">
      <h3 className="text-lg text-slate-300 mb-8 text-center">
        {parsedFile.path}
      </h3>
      {parsedFile.tracks.map((t) => (
        <h5>{t.name}</h5>
      ))}
      {parsedFile.channels.map((channel, i) => (
        <div className="mb-2" key={i}>
          <h5 className="text-xs text-slate-300">
            {i} (Channel {channel.index}){" "}
          </h5>
          {channel.instruments.map((instrument, j) => (
            <Fragment key={j}>
              <h5 className="text-xs text-slate-500 pl-4">
                Instrument {j}{" "}
                {instrument.program !== null
                  ? `(${
                      generalMIDI[
                        instrument.program as keyof typeof generalMIDI
                      ]
                    })`
                  : ""}
              </h5>
              <NoteLane
                notes={instrument.notes}
                end={end}
                highlights={highlights.filter(
                  (h) => h.channel === i && h.instrument === j
                )}
                channel={i}
                instrument={j}
                playing={playing}
                setPlaying={setPlaying}
              />
            </Fragment>
          ))}
        </div>
      ))}
    </div>
  );
};

export default function Home() {
  const { data: fileList, error } = useSWR("files", fileListFetcher);

  const [index, setIndex] = useState(0);

  const synthesizerRef = useRef<Synthesizer | null>(null);

  useEffect(() => {
    synthesizerRef.current = new Synthesizer();
    return () => {
      synthesizerRef.current?.destroy();
    };
  }, []);

  useEffect(() => {
    (window as any).filterExamples = async () => {
      if (!fileList) return;
      const slicedList = fileList.slice(10, 110);
      let numRemoved = 0;
      let numKept = 0;
      const removalReasons: { [key: string]: Note[][] } = {};
      await Promise.all(
        slicedList.map(async (file) => {
          const res = await fetch(`${SERVER_URL}/midi?id=eq.${file}`);
          const json = await res.json();
          const parsed = json[0] as ParsedMIDIFile;
          const examples = getFileExamples(parsed, getHighlights(parsed));
          examples.forEach((example) => {
            if (example.rejection_reason) {
              numRemoved++;
              if (!removalReasons[example.rejection_reason]) {
                removalReasons[example.rejection_reason] = [];
              }
              removalReasons[example.rejection_reason].push(example.example)
            } else {
              numKept ++;
            }
          });
          return examples;
        })
      );
      console.log(
        `Removed ${numRemoved} examples (${(
          (numRemoved / (numKept + numRemoved)) *
          100
        ).toFixed(2)}%)`
      );
      console.log(removalReasons);
    };

    (window as any).analyzeExamples = async () => {
      if (!fileList) return;
      const slicedList = fileList.slice(0, 10);

      const examples = (await Promise.all(
        slicedList.map(async (file) => {
          const res = await fetch(`${SERVER_URL}/midi?id=eq.${file}`);
          const json = await res.json();
          const parsed = json[0] as ParsedMIDIFile;
          const examples = getFileExamples(parsed, getHighlights(parsed));
          return examples;
        })
      )).flat();

      console.log(`Analyzed ${examples.length} examples from ${slicedList.length} files`);
      console.log(`Total accompaniments: ${examples.map((e) => e.accompaniments).reduce((a, c) => a + c.length, 0)}`);
      console.log(examples);
    }

    (window as any).spotCheck = async (fileOverride?: string) => {
      if (!fileList) return;
      const file =
        fileOverride || fileList[Math.floor(Math.random() * fileList.length)];
      const res = await fetch(`${SERVER_URL}/midi?id=eq.${file}`);
      const json = await res.json();
      const parsed = json[0] as ParsedMIDIFile;
      console.log("Fetched", file, `(${parsed.path})`);
      const examples = getFileExamples(parsed, getHighlights(parsed));
      const nonRejectedExamplesWithAccompaniments = examples.filter((e) => !e.rejection_reason && e.accompaniments.length > 0);
      const testExample = nonRejectedExamplesWithAccompaniments[Math.floor(Math.random() * nonRejectedExamplesWithAccompaniments.length)];
      if (!testExample) {
        console.log('no accompaniments found!');
        return;
      }
      const allNotes = [
        ...testExample.example
      ];
      testExample.accompaniments.forEach((accompaniment) => {
        allNotes.push(...accompaniment);
      });
      synthesizerRef.current?.destroy();
      synthesizerRef.current = new Synthesizer();
      synthesizerRef.current?.setNotes(allNotes, true);
      synthesizerRef.current?.play();
    }
  }, [fileList]);

  const [tagMode, setTagMode] = useState(false);

  useEffect(() => {
    const handleKeydown = (e: any) => {
      if (!tagMode && e.key === "ArrowLeft") {
        setIndex(Math.max(0, index - 1));
      } else if (!tagMode && e.key === "ArrowRight") {
        setIndex(Math.min(fileList ? fileList.length - 1 : 0, index + 1));
      } else if (e.key === "+") {
        synthesizerRef.current?.setTempoOffset(
          synthesizerRef.current.tempoOffset + 5
        );
      } else if (e.key === "-") {
        synthesizerRef.current?.setTempoOffset(
          synthesizerRef.current.tempoOffset - 5
        );
      } else if (e.key === "6") {
        setTagMode(!tagMode);
      }
    };
    window.addEventListener("keydown", handleKeydown);
    return () => {
      window.removeEventListener("keydown", handleKeydown);
    };
  }, [fileList, index, tagMode]);

  if (!fileList) return <div />;

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-1">
      {tagMode && <TaggingUI synthesizerRef={synthesizerRef} />}
      <div className="flex mb-10">
        <button
          className="uppercase text-xs border rounded p-1"
          onClick={() => setIndex(Math.max(0, index - 1))}
        >
          Prev
        </button>
        <h4 className="text-slate-700 mx-10 text-center" style={{ width: 800 }}>
          {fileList[index]}
        </h4>
        <button
          className="uppercase text-xs border rounded p-1"
          onClick={() => setIndex(Math.min(fileList.length - 1, index + 1))}
        >
          Next
        </button>
      </div>
      {fileList && synthesizerRef.current && (
        <MIDIDisplay
          file={fileList[index]}
          synthesizer={synthesizerRef.current}
        />
      )}
    </main>
  );
}
