import {
  App,
  MarkdownPostProcessorContext,
  MarkdownRenderChild,
} from "obsidian";
import type { TodoistApi } from "./api/api";
import debug from "./log";
import IQuery, { parseQuery } from "./query";
import { Result } from "./result";
import TodoistQuery from "./ui/TodoistQuery.svelte";
import ErrorDisplay from "./ui/ErrorDisplay.svelte";
import type SvelteComponentDev from "./ui/TodoistQuery.svelte";

export default class QueryInjector {
  private api: TodoistApi;
  private app: App;
  private pendingQueries: PendingQuery[];

  constructor(app: App) {
    this.app = app;
    this.pendingQueries = [];
  }

  onNewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
    const pendingQuery = {
      source: source,
      target: el,
      ctx: ctx,
    };

    if (typeof this.api == "undefined") {
      this.pendingQueries.push(pendingQuery);
      return;
    }

    this.injectQuery(pendingQuery);
  }

  setApi(api: TodoistApi) {
    this.api = api;

    while (this.pendingQueries.length > 0) {
      this.injectQuery(this.pendingQueries[0]);
      this.pendingQueries.splice(0, 1);
    }
  }

  injectQuery(pendingQuery: PendingQuery) {
    let query: Result<IQuery, Error> = null;

    try {
      query = parseQuery(JSON.parse(pendingQuery.source));
    } catch (e) {
      query = Result.Err(new Error(`Query was not valid JSON: ${e.message}.`));
    }

    debug({
      msg: "Parsed query",
      context: query,
    });

    const child = new InjectedQuery(pendingQuery.target, (root: HTMLElement) => {
      if (query.isOk()) {
        return new TodoistQuery({
          target: root,
          props: {
            query: query.unwrap(),
            api: this.api,
            app: this.app,
          },
        });
      } else {
        return new ErrorDisplay({
          target: root,
          props: {
            error: query.unwrapErr(),
          },
        });
      }
    });

    pendingQuery.ctx.addChild(child);
  }
}

interface PendingQuery {
  source: string;
  target: HTMLElement;
  ctx: MarkdownPostProcessorContext;
}

class InjectedQuery extends MarkdownRenderChild {
  private readonly createComp: (root: HTMLElement) => SvelteComponentDev;
  private component: SvelteComponentDev;

  constructor(
    container: HTMLElement,
    createComp: (root: HTMLElement) => SvelteComponentDev
  ) {
    super(container);
    this.createComp = createComp;
    this.containerEl = container;
  }

  onload() {
    this.component = this.createComp(this.containerEl);
  }

  onunload() {
    if (this.component) {
      this.component.$destroy();
    }
  }
}