import { RenderContent, RenderPath, RenderSelection } from "../types/renderer";
import { UDraft } from "./draft";
import { UModel } from "./model";
import { $attr } from "../shortcuts/queries";
import { _ref, _rootModule } from "../shortcuts/attributes";
import { UModule } from "./module";
import { UFeature } from "./feature";

export class URenderer {
  private _draft?: UDraft;
  private _selection: RenderSelection = {};
  private _contents: RenderContent[] = [];
  private _outputs: RenderContent[] = [];
  private _name: string;

  constructor(name: string) {
    this._name = name;
  }

  $name() {
    return this._name;
  }

  $draft() {
    return this._draft as UDraft;
  }

  $selection() {
    return { ...this._selection };
  }

  $paths() {
    return [...(this._selection.paths || [])];
  }

  $contents() {
    return [...this._contents];
  }

  $outputs() {
    return [...this._outputs];
  }

  $path(key: string) {
    return this.$paths().find((f) => f.key == key) || null;
  }

  $content(key: string) {
    return this.$contents().find((f) => f.key == key) || null;
  }

  $output(key: string) {
    return this.$outputs().find((f) => f.key == key) || null;
  }

  $modules(where?: (module: UModule) => boolean) {
    const modules: UModule[] = this.$draft()
      .$modules()
      .filter((mod) => (where ? where(mod) : true));
    return modules;
  }

  $features(where?: (module: UModule, feature: UFeature) => boolean) {
    const modules = this.$draft().$modules();
    const features: UFeature[] = [];

    modules.forEach((mod) => {
      let foundFeatures = mod.$features();
      if (where)
        foundFeatures = foundFeatures.filter((feature) => where(mod, feature));
      features.push(...foundFeatures);
    });

    return features;
  }

  $models(where?: (module: UModule, model: UModel) => boolean) {
    const modules = this.$draft().$modules();
    let models: UModel[] = [];

    modules.forEach((mod) => {
      let foundModels: UModel[] = [...mod.$models()];
      mod.$features().forEach((feature) => {
        const root: UModel[] = [];
        if (feature.$input()) root.push(feature.$input() as UModel);
        if (feature.$output()) root.push(feature.$output() as UModel);

        const modelIsAlreadyRegistered = (model: UModel) =>
          foundModels.some((m) => m.$name() == model.$name()) ||
          models.some((m) => m.$name() == model.$name());

        const deepSearch = (model: UModel) => {
          const modelRootModule = $attr(model, _rootModule());
          const isAlreadyRegistered = modelIsAlreadyRegistered(model);
          if (
            !isAlreadyRegistered ||
            (modelRootModule && modelRootModule.$name() == mod.$name())
          ) {
            if (isAlreadyRegistered) {
              foundModels = foundModels.filter(
                (m) => m.$name() != model.$name()
              );
              models = models.filter((m) => m.$name() != model.$name());
            }
            foundModels.push(model);
            model.$fields().forEach((field) => {
              if (field.$type() == "nested" || field.$type() == "reference") {
                const referencedModel = $attr(field, _ref());
                if (referencedModel && referencedModel.$name() != model.$name())
                  deepSearch(referencedModel);
              }
            });
          }
        };
        root.forEach(deepSearch);
      });

      if (where) foundModels = foundModels.filter((model) => where(mod, model));
      models.push(...foundModels);
    });

    return models;
  }

  $resolveRelativePath(from: string, to: string): string {
    const toFileName = to.split("/").slice(-1)[0];
    const fromParts = from
      .split("/")
      .filter((v) => !!v)
      .slice(0, -1);
    const toParts = to
      .split("/")
      .filter((v) => !!v)
      .slice(0, -1);
    for (let i = 0; i <= fromParts.length; i++) {
      const pathIsSplitting =
        fromParts.slice(0, i).join("/") !== toParts.slice(0, i).join("/");
      if (
        pathIsSplitting ||
        (i == fromParts.length && fromParts.length < toParts.length)
      ) {
        let returns = "";
        if (pathIsSplitting)
          for (let j = 0; j <= fromParts.length - i; j++) returns += "../";

        return `${returns ? returns : "./"}${toParts
          .slice(pathIsSplitting ? i - 1 : i)
          .join("/")}/${toFileName}`;
      }
    }
    return `./${toFileName}`;
  }

  async init(draft: UDraft) {
    this._draft = draft;
    this._selection = await this.select();
    return this;
  }

  async run(contents: RenderContent[]) {
    this._contents = contents;
    this._outputs = await this.render();
    this._outputs.forEach((output) => {
      output.meta = this.$content(output.key)?.meta || {};
    });
    return this;
  }

  async select(): Promise<RenderSelection> {
    return {};
  }

  async render(): Promise<RenderContent[]> {
    return [];
  }

  clear() {
    this._draft = undefined;
    this._selection = {};
    this._contents = [];
    this._outputs = [];
  }
}
