import { buildResolver, setResolverOption } from "discourse-common/resolver";
import { module, test } from "qunit";
import { registerTemporaryModule } from "discourse/tests/helpers/temporary-module-helper";
import DiscourseTemplateMap from "discourse-common/lib/discourse-template-map";

let resolver;

function lookupTemplate(assert, name, expectedTemplate, message) {
  let parseName = resolver.parseName(name);
  let result = resolver.resolveTemplate(parseName);
  assert.strictEqual(result, expectedTemplate, message);
}

function setTemplates(templateModuleNames) {
  for (const name of templateModuleNames) {
    registerTemporaryModule(name, name);
  }
}

const DiscourseResolver = buildResolver("discourse");

module("Unit | Ember | resolver", function (hooks) {
  hooks.beforeEach(function () {
    DiscourseTemplateMap.setModuleNames(Object.keys(requirejs.entries));
    resolver = DiscourseResolver.create({
      namespace: { modulePrefix: "discourse" },
    });
  });

  test("finds templates in top level dir", function (assert) {
    setTemplates([
      "discourse/templates/foobar",
      "discourse/templates/fooBar",
      "discourse/templates/foo_bar",
      "discourse/templates/foo.bar",
    ]);

    // Default unmodified behavior
    lookupTemplate(
      assert,
      "template:foobar",
      "discourse/templates/foobar",
      "by lowcased name"
    );

    // Default unmodified behavior
    lookupTemplate(
      assert,
      "template:fooBar",
      "discourse/templates/fooBar",
      "by camel cased name"
    );

    // Default unmodified behavior
    lookupTemplate(
      assert,
      "template:foo_bar",
      "discourse/templates/foo_bar",
      "by underscored name"
    );

    // Default unmodified behavior
    lookupTemplate(
      assert,
      "template:foo.bar",
      "discourse/templates/foo.bar",
      "by dotted name"
    );
  });

  test("finds templates in first-level subdir", function (assert) {
    setTemplates(["discourse/templates/foo/bar_baz"]);

    // Default unmodified behavior
    lookupTemplate(
      assert,
      "template:foo/bar_baz",
      "discourse/templates/foo/bar_baz",
      "with subdir defined by slash"
    );

    // Convert dots to slash
    lookupTemplate(
      assert,
      "template:foo.bar_baz",
      "discourse/templates/foo/bar_baz",
      "with subdir defined by dot"
    );

    // Convert dashes to slash
    lookupTemplate(
      assert,
      "template:foo-bar_baz",
      "discourse/templates/foo/bar_baz",
      "with subdir defined by dash"
    );

    // Underscored with first segment as directory
    lookupTemplate(
      assert,
      "template:fooBarBaz",
      "discourse/templates/foo/bar_baz",
      "with subdir defined by first camel case and the rest of camel cases converted to underscores"
    );

    // Already underscored with first segment as directory
    lookupTemplate(
      assert,
      "template:foo_bar_baz",
      "discourse/templates/foo/bar_baz",
      "with subdir defined by first underscore"
    );
  });

  test("resolves precedence between overlapping top level dir and first level subdir templates", function (assert) {
    setTemplates([
      "discourse/templates/fooBar",
      "discourse/templates/foo_bar",
      "discourse/templates/foo.bar",
      "discourse/templates/foo/bar",
      "discourse/templates/baz/qux",
    ]);

    // Directories are prioritized when dotted
    lookupTemplate(
      assert,
      "template:foo.bar",
      "discourse/templates/foo/bar",
      "preferring first level subdir for dotted name"
    );

    // Directories are prioritized when dashed
    lookupTemplate(
      assert,
      "template:foo-bar",
      "discourse/templates/foo/bar",
      "preferring first level subdir for dotted name"
    );

    // Default unmodified before directories, except when dotted
    lookupTemplate(
      assert,
      "template:fooBar",
      "discourse/templates/fooBar",
      "preferring top level dir for camel cased name"
    );

    // Default unmodified before directories, except when dotted
    lookupTemplate(
      assert,
      "template:foo_bar",
      "discourse/templates/foo_bar",
      "preferring top level dir for underscored name"
    );

    // Use directory version if top-level isn't found
    lookupTemplate(
      assert,
      "template:baz-qux",
      "discourse/templates/baz/qux",
      "fallback subdir for dashed name"
    );
  });

  test("finds templates in subdir deeper than one level", function (assert) {
    setTemplates(["discourse/templates/foo/bar/baz/qux"]);

    // Default unmodified
    lookupTemplate(
      assert,
      "template:foo/bar/baz/qux",
      "discourse/templates/foo/bar/baz/qux",
      "for subdirs defined by slashes"
    );

    // Converts dotted to slashed
    lookupTemplate(
      assert,
      "template:foo.bar.baz.qux",
      "discourse/templates/foo/bar/baz/qux",
      "for subdirs defined by dots"
    );

    // Converts first camelized segment to slashed
    lookupTemplate(
      assert,
      "template:foo/bar/bazQux",
      "discourse/templates/foo/bar/baz/qux",
      "for subdirs defined by slashes plus one camel case"
    );

    // Converts first underscore to slashed
    lookupTemplate(
      assert,
      "template:foo/bar/baz_qux",
      "discourse/templates/foo/bar/baz/qux",
      "for subdirs defined by slashes plus one underscore"
    );

    // Only converts first camelized segment to slashed so this isn't matched
    lookupTemplate(
      assert,
      "template:fooBarBazQux",
      undefined,
      "but not for subdirs defined by more than one camel case"
    );

    // Only converts first underscored segment to slashed so this isn't matched
    lookupTemplate(
      assert,
      "template:foo_bar_baz_qux",
      undefined,
      "but not for subdirs defined by more than one underscore"
    );

    // Only converts dots to slashes OR first camelized segment. This has both so isn't matched.
    lookupTemplate(
      assert,
      "template:foo.bar.bazQux",
      undefined,
      "but not for subdirs defined by dots plus one camel case"
    );

    // Only converts dots to slashes OR first underscored segment. This has both so isn't matched.
    lookupTemplate(
      assert,
      "template:foo.bar.baz_qux",
      undefined,
      "but not for subdirs defined by dots plus one underscore"
    );
  });

  test("resolves mobile templates to 'mobile/' namespace", function (assert) {
    setTemplates([
      "discourse/templates/mobile/foo",
      "discourse/templates/bar",
      "discourse/templates/mobile/bar",
      "discourse/templates/baz",
    ]);

    setResolverOption("mobileView", true);

    // Default with mobile/ added
    lookupTemplate(
      assert,
      "template:foo",
      "discourse/templates/mobile/foo",
      "finding mobile version even if normal one is not present"
    );

    // Default with mobile preferred
    lookupTemplate(
      assert,
      "template:bar",
      "discourse/templates/mobile/bar",
      "preferring mobile version when both mobile and normal versions are present"
    );

    // Default when mobile not present
    lookupTemplate(
      assert,
      "template:baz",
      "discourse/templates/baz",
      "falling back to a normal version when mobile version is not present"
    );
  });

  test("resolves templates to plugin and theme namespaces", function (assert) {
    setTemplates([
      "discourse/plugins/my-plugin/discourse/templates/foo",
      "discourse/templates/bar",
      "discourse/plugins/my-plugin/discourse/templates/bar",
      "discourse/templates/baz",
      "discourse/plugins/my-plugin/discourse/templates/baz",
      "discourse/theme-12/discourse/templates/baz",
      "discourse/templates/qux",
    ]);

    // Defined in plugin only
    lookupTemplate(
      assert,
      "template:foo",
      "discourse/plugins/my-plugin/discourse/templates/foo",
      "finding plugin version even if normal one is not present"
    );

    // Defined in core and plugin
    lookupTemplate(
      assert,
      "template:bar",
      "discourse/plugins/my-plugin/discourse/templates/bar",
      "prefers plugin version over core"
    );

    // Defined in core and plugin and theme
    lookupTemplate(
      assert,
      "template:baz",
      "discourse/theme-12/discourse/templates/baz",
      "prefers theme version over plugin and core"
    );

    // Defined in core only
    lookupTemplate(
      assert,
      "template:qux",
      "discourse/templates/qux",
      "uses core if there are no theme/plugin definitions"
    );
  });

  test("resolves plugin mobile templates", function (assert) {
    setTemplates([
      "discourse/plugins/my-plugin/discourse/templates/mobile/foo",
      "discourse/plugins/my-plugin/discourse/templates/mobile/bar",
      "discourse/plugins/my-plugin/discourse/templates/bar",
      "discourse/plugins/my-plugin/discourse/templates/mobile/baz",
      "discourse/templates/mobile/baz",
    ]);

    setResolverOption("mobileView", true);

    // Default with plugin template override
    lookupTemplate(
      assert,
      "template:foo",
      "discourse/plugins/my-plugin/discourse/templates/mobile/foo",
      "finding plugin version even if normal one is not present"
    );

    // Default with plugin mobile added, takes precedence over non-mobile
    lookupTemplate(
      assert,
      "template:bar",
      "discourse/plugins/my-plugin/discourse/templates/mobile/bar",
      "preferring plugin mobile version when both non-mobile plugin version is also present"
    );

    // Default with when non-plugin mobile version is present
    lookupTemplate(
      assert,
      "template:baz",
      "discourse/plugins/my-plugin/discourse/templates/mobile/baz",
      "preferring plugin mobile version over non-plugin mobile version"
    );
  });

  test("resolves templates with 'admin' prefix", function (assert) {
    setTemplates([
      "admin/templates/foo",
      "discourse/templates/adminBar",
      "discourse/templates/admin_bar",
      "discourse/templates/admin.bar",
      "admin/templates/bar",
      "admin/templates/dashboard_general",
      "discourse/templates/admin-baz-qux",
      "discourse/plugins/my-plugin/discourse/templates/admin/plugin-template",
      "admin/templates/components/my-admin-component",
    ]);

    // Switches prefix to admin/templates when camelized
    lookupTemplate(
      assert,
      "template:adminFoo",
      "admin/templates/foo",
      "when prefix is separated by camel case"
    );

    // Switches prefix to admin/templates when underscored
    lookupTemplate(
      assert,
      "template:admin_foo",
      "admin/templates/foo",
      "when prefix is separated by underscore"
    );

    // Switches prefix to admin/templates when dotted
    lookupTemplate(
      assert,
      "template:admin.foo",
      "admin/templates/foo",
      "when prefix is separated by dot"
    );

    // Doesn't match unseparated prefix
    lookupTemplate(
      assert,
      "template:adminfoo",
      undefined,
      "but not when prefix is not separated in any way"
    );

    // Prioritized the default match when camelized
    lookupTemplate(
      assert,
      "template:adminBar",
      "discourse/templates/adminBar",
      "but not when template with the exact camel cased name exists"
    );

    // Prioritized the default match when underscored
    lookupTemplate(
      assert,
      "template:admin_bar",
      "discourse/templates/admin_bar",
      "but not when template with the exact underscored name exists"
    );

    // Prioritized the default match when dotted
    lookupTemplate(
      assert,
      "template:admin.bar",
      "discourse/templates/admin.bar",
      "but not when template with the exact dotted name exists"
    );

    lookupTemplate(
      assert,
      "template:admin-dashboard-general",
      "admin/templates/dashboard_general",
      "finds namespaced and underscored version"
    );

    lookupTemplate(
      assert,
      "template:admin-baz/qux",
      "discourse/templates/admin-baz-qux",
      "also tries dasherized"
    );

    lookupTemplate(
      assert,
      "template:admin-plugin/template",
      "discourse/plugins/my-plugin/discourse/templates/admin/plugin-template",
      "looks up templates in plugins"
    );

    lookupTemplate(
      assert,
      "template:foo",
      undefined,
      "doesn't return admin templates for regular controllers"
    );

    lookupTemplate(
      assert,
      "template:components/my-admin-component",
      "admin/templates/components/my-admin-component",
      "returns admin-defined component templates"
    );
  });

  test("resolves component templates with 'admin' prefix to 'admin/templates/' namespace", function (assert) {
    setTemplates([
      "admin/templates/components/foo",
      "discourse/templates/components/bar",
      "admin/templates/components/bar",
    ]);

    // Looks for components in admin/templates
    lookupTemplate(
      assert,
      "template:components/foo",
      "admin/templates/components/foo",
      "uses admin template component when no standard match"
    );

    // Prioritized non-admin component
    lookupTemplate(
      assert,
      "template:components/bar",
      "discourse/templates/components/bar",
      "uses standard match when both exist"
    );
  });

  // We can probably remove this in the future since this behavior seems pretty
  // close to Ember's default behavior.
  // See https://guides.emberjs.com/release/routing/loading-and-error-substates/
  test("resolves loading templates", function (assert) {
    setTemplates([
      "discourse/templates/fooloading",
      "discourse/templates/foo/loading",
      "discourse/templates/foo_loading",
      "discourse/templates/loading",
    ]);

    lookupTemplate(
      assert,
      "template:fooloading",
      "discourse/templates/fooloading",
      "exact match without separator"
    );

    lookupTemplate(
      assert,
      "template:foo/loading",
      "discourse/templates/foo/loading",
      "exact match with slash"
    );

    lookupTemplate(
      assert,
      "template:foo_loading",
      "discourse/templates/foo_loading",
      "exact match underscore"
    );

    lookupTemplate(
      assert,
      "template:barloading",
      "discourse/templates/loading",
      "fallback without separator"
    );

    lookupTemplate(
      assert,
      "template:bar/loading",
      "discourse/templates/loading",
      "fallback with slash"
    );

    lookupTemplate(
      assert,
      "template:bar.loading",
      "discourse/templates/loading",
      "fallback with dot"
    );

    lookupTemplate(
      assert,
      "template:bar_loading",
      "discourse/templates/loading",
      "fallback underscore"
    );

    // TODO: Maybe test precedence
  });

  test("resolves connector templates", function (assert) {
    setTemplates([
      "discourse/plugins/my-plugin/discourse/templates/foo",
      "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/baz_qux",
      "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase",
    ]);

    lookupTemplate(
      assert,
      "template:connectors/foo",
      "discourse/plugins/my-plugin/discourse/templates/foo",
      "looks up in plugin namespace"
    );

    lookupTemplate(
      assert,
      "template:connectors/components/foo",
      "discourse/plugins/my-plugin/discourse/templates/foo",
      "removes components segment"
    );

    lookupTemplate(
      assert,
      "template:connectors/foo-bar/baz-qux",
      "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/baz_qux",
      "underscores last segment"
    );

    lookupTemplate(
      assert,
      "template:connectors/foo-bar/camelCase",
      "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase",
      "handles camelcase file names"
    );

    lookupTemplate(
      assert,
      resolver.normalize("template:connectors/foo-bar/camelCase"),
      "discourse/plugins/my-plugin/discourse/templates/connectors/foo-bar/camelCase",
      "handles camelcase file names when normalized"
    );
  });

  test("returns 'not_found' template when template name cannot be resolved", function (assert) {
    setTemplates(["discourse/templates/not_found"]);

    lookupTemplate(
      assert,
      "template:foo/bar/baz",
      "discourse/templates/not_found",
      ""
    );
  });

  test("resolves templates with 'wizard' prefix", function (assert) {
    setTemplates([
      "wizard/templates/foo",
      "discourse/templates/wizard_bar",
      "discourse/templates/wizard.bar",
      "wizard/templates/bar",
      "wizard/templates/dashboard_general",
      "discourse/templates/wizard-baz-qux",
      "javascripts/wizard/plugin-template",
    ]);

    // Switches prefix to wizard/templates when underscored
    lookupTemplate(
      assert,
      "template:wizard_foo",
      "wizard/templates/foo",
      "when prefix is separated by underscore"
    );

    // Switches prefix to wizard/templates when dotted
    lookupTemplate(
      assert,
      "template:wizard.foo",
      "wizard/templates/foo",
      "when prefix is separated by dot"
    );

    // Doesn't match unseparated prefix
    lookupTemplate(
      assert,
      "template:wizardfoo",
      undefined,
      "but not when prefix is not separated in any way"
    );

    // Prioritized the default match when underscored
    lookupTemplate(
      assert,
      "template:wizard_bar",
      "discourse/templates/wizard_bar",
      "but not when template with the exact underscored name exists"
    );

    // Prioritized the default match when dotted
    lookupTemplate(
      assert,
      "template:wizard.bar",
      "discourse/templates/wizard.bar",
      "but not when template with the exact dotted name exists"
    );

    lookupTemplate(
      assert,
      "template:wizard-dashboard-general",
      "wizard/templates/dashboard_general",
      "finds namespaced and underscored version"
    );

    lookupTemplate(
      assert,
      "template:wizard-baz/qux",
      "discourse/templates/wizard-baz-qux",
      "also tries dasherized"
    );
  });

  test("resolves component templates with 'wizard' prefix to 'wizard/templates/' namespace", function (assert) {
    setTemplates([
      "wizard/templates/components/foo",
      "discourse/templates/components/bar",
      "wizard/templates/components/bar",
    ]);

    // Looks for components in wizard/templates
    lookupTemplate(
      assert,
      "template:components/foo",
      "wizard/templates/components/foo",
      "uses wizard template component when no standard match"
    );

    // Prioritized non-wizard component
    lookupTemplate(
      assert,
      "template:components/bar",
      "discourse/templates/components/bar",
      "uses standard match when both exist"
    );
  });
});
