text: ;(async function() {
    //Init
    const { data: metadata } = await axios.get("/.plugins.metadata")
    delete metadata.core.web.output
    delete metadata.core.web.twemojis
    //App
    return new Vue({
      //Initialization
      el: "main",
      async mounted() {
        //Interpolate config from browser
        try {
          this.config.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
          this.palette = (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
        }
        catch (error) {}
        //Init
        await Promise.all([
          //GitHub limit tracker
          (async () => {
            const { data: requests } = await axios.get("/.requests")
            this.requests = requests
          })(),
          //Templates
          (async () => {
            const { data: templates } = await axios.get("/.templates")
            templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
            this.templates.list = templates
            this.templates.selected = templates[0]?.name || "classic"
          })(),
          //Plugins
          (async () => {
            const { data: plugins } = await axios.get("/.plugins")
            this.plugins.list = plugins.filter(({name}) => metadata[name]?.supports.includes("user") || metadata[name]?.supports.includes("organization"))
          })(),
          //Base
          (async () => {
            const { data: base } = await axios.get("/.plugins.base")
            this.plugins.base = base
            this.plugins.enabled.base = Object.fromEntries(base.map(key => [key, true]))
          })(),
          //Version
          (async () => {
            const { data: version } = await axios.get("/.version")
            this.version = `v${version}`
          })(),
          //Hosted
          (async () => {
            const { data: hosted } = await axios.get("/.hosted")
            this.hosted = hosted
          })(),
        ])
        //Generate placeholder
        this.mock({ timeout: 200 })
        setInterval(() => {
          const marker = document.querySelector("#metrics-end")
          if (marker) {
            this.mockresize()
            marker.remove()
          }
        }, 100)
      },
      components: { Prism: PrismComponent },
      //Watchers
      watch: {
        tab: {
          immediate: true,
          handler(current) {
            if (current === 'action') this.clipboard = new ClipboardJS('.copy-action')
            else this.clipboard?.destroy()
          },
        },
        palette: {
          immediate: true,
          handler(current, previous) {
            document.querySelector("body").classList.remove(previous)
            document.querySelector("body").classList.add(current)
          },
        },
      },
      //Data initialization
      data: {
        version: "",
        user: "",
        mode: "metrics",
        tab: "overview",
        palette: "light",
        clipboard: null,
        requests: { limit: 0, used: 0, remaining: 0, reset: 0 },
        cached: new Map(),
        config: Object.fromEntries(Object.entries(metadata.core.web).map(([key, { defaulted }]) => [key, defaulted])),
        metadata: Object.fromEntries(Object.entries(metadata).map(([key, { web }]) => [key, web])),
        hosted: null,
        plugins: {
          base: {},
          list: [],
          enabled: {},
          descriptions: {
            base: "🗃️ Base content",
            "base.header": "Header",
            "base.activity": "Account activity",
            "base.community": "Community stats",
            "base.repositories": "Repositories metrics",
            "base.metadata": "Metadata",
            ...Object.fromEntries(Object.entries(metadata).map(([key, { name }]) => [key, name])),
          },
          options: {
            descriptions: { ...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web))) },
            ...(Object.fromEntries(
              Object.entries(
                Object.assign({}, ...Object.entries(metadata).flatMap(([key, { web }]) => web)),
              )
                .map(([key, { defaulted }]) => [key, defaulted]),
            )),
          },
        },
        templates: {
          list: [],
          selected: "classic",
          placeholder: {
            timeout: null,
            image: "",
          },
          descriptions: {
            classic: "Classic template",
            terminal: "Terminal template",
            markdown: "(hidden)",
            repository: "(hidden)",
          },
        },
        generated: {
          pending: false,
          content: "",
          error: false,
        },
      },
      //Computed data
      computed: {
        //Unusable plugins
        unusable() {
          return this.plugins.list.filter(({ name }) => this.plugins.enabled[name]).filter(({ enabled }) => !enabled).map(({ name }) => name)
        },
        //User's avatar
        avatar() {
          return this.generated.content ? `https://github.com/${this.user}.png` : null
        },
        //User's repository
        repo() {
          return `https://github.com/${this.user}/${this.user}`
        },
        //Endpoint to use for computed metrics
        url() {
          //Plugins enabled
          const plugins = Object.entries(this.plugins.enabled)
            .flatMap(([key, value]) => key === "base" ? Object.entries(value).map(([key, value]) => [`base.${key}`, value]) : [[key, value]])
            .filter(([key, value]) => /^base[.]\w+$/.test(key) ? !value : value)
            .map(([key, value]) => `${key}=${+value}`)
          //Plugins options
          const options = Object.entries(this.plugins.options)
            .filter(([key, value]) => `${value}`.length)
            .filter(([key, value]) => this.plugins.enabled[key.split(".")[0]])
            .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
          //Base options
          const base = Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
          //Config
          const config = Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => `config.${key}=${encodeURIComponent(value)}`)
          //Template
          const template = (this.templates.selected !== this.templates.list[0]) ? [`template=${this.templates.selected}`] : []
          //Generated url
          const params = [...template, ...base, ...plugins, ...options, ...config].join("&")
          return `${window.location.protocol}//${window.location.host}/${this.user}${params.length ? `?${params}` : ""}`
        },
        //Embedded generated code
        embed() {
          return `![Metrics](${this.url})`
        },
        //Token scopes
        scopes() {
          return new Set([
            ...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).flatMap(([key]) => metadata[key].scopes),
            ...(Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).length ? metadata.base.scopes : [])
          ])
        },
        //GitHub action auto-generated code
        action() {
          return [
            `# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`,
            `name: Metrics`,
            `on:`,
            `  # Schedule updates (each hour)`,
            `  schedule: [{cron: "0 * * * *"}]`,
            `  # Lines below let you run workflow manually and on each commit`,
            `  workflow_dispatch:`,
            `  push: {branches: ["master", "main"]}`,
            `jobs:`,
            `  github-metrics:`,
            `    runs-on: ubuntu-latest`,
            `    steps:`,
            `      - uses: lowlighter/metrics@latest`,
            `        with:`,
            ...(this.scopes.size ? [
            `          # Your GitHub token`,
            `          # The following scopes are required:`,
            ...[...this.scopes].map(scope => `          #  - ${scope}${scope === "public_access" ? " (default scope)" : ""}`),
            `          # The following additional scopes may be required:`,
            `          #  - read:org  (for organization related metrics)`,
            `          #  - read:user (for user related data)`,
            `          #  - repo      (optional, if you want to include private repositories)`
            ] : [
            `          # Current configuration doesn't require a GitHub token`,
            ]),
            `          token: ${this.scopes.size ? `${"$"}{{ secrets.METRICS_TOKEN }}` : "NOT_NEEDED"}`,
            ``,
            `          # Options`,
            `          user: ${this.user}`,
            `          template: ${this.templates.selected}`,
            `          base: ${Object.entries(this.plugins.enabled.base).filter(([key, value]) => value).map(([key]) => key).join(", ") || '""'}`,
            ...[
              ...Object.entries(this.plugins.options).filter(([key, value]) => (key in metadata.base.web) && (value !== metadata.base.web[key]?.defaulted)).map(([key, value]) =>
                `          ${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`
              ),
              ...Object.entries(this.plugins.enabled).filter(([key, value]) => (key !== "base") && (value)).map(([key]) => `          plugin_${key}: yes`),
              ...Object.entries(this.plugins.options).filter(([key, value]) => value).filter(([key, value]) => this.plugins.enabled[key.split(".")[0]]).map(([key, value]) =>
                `          plugin_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`
              ),
              ...Object.entries(this.config).filter(([key, value]) => (value) && (value !== metadata.core.web[key]?.defaulted)).map(([key, value]) => `          config_${key.replace(/[.]/g, "_")}: ${typeof value === "boolean" ? { true: "yes", false: "no" }[value] : value}`),
            ].sort(),
          ].join("\n")
        },
        //Configurable plugins
        configure() {
          //Check enabled plugins
          const enabled = Object.entries(this.plugins.enabled).filter(([key, value]) => (value) && (key !== "base")).map(([key, value]) => key)
          const filter = new RegExp(`^(?:${enabled.join("|")})[.]`)
          //Search related options
          const entries = Object.entries(this.plugins.options.descriptions).filter(([key, value]) => (filter.test(key)) && (!(key in metadata.base.web)))
          entries.push(...enabled.map(key => [key, this.plugins.descriptions[key]]))
          entries.sort((a, b) => a[0].localeCompare(b[0]))
          //Return object
          const configure = Object.fromEntries(entries)
          return Object.keys(configure).length ? configure : null
        },
        //Is in preview mode
        preview() {
          return /-preview$/.test(this.version)
        },
      },
      //Methods
      methods: {
        //Refresh computed properties
        async refresh() {
          const keys = {action:["scopes", "action"], markdown:["url", "embed"]}[this.tab]
          if (keys) {
            for (const key of keys)
              this._computedWatchers[key]?.run()
            this.$forceUpdate()
          }
        },
        //Load and render placeholder image
        async mock({ timeout = 600 } = {}) {
          this.refresh()
          clearTimeout(this.templates.placeholder.timeout)
          this.templates.placeholder.timeout = setTimeout(async () => {
            this.templates.placeholder.image = await placeholder(this)
            this.generated.content = ""
            this.generated.error = null
          }, timeout)
        },
        //Resize mock image
        mockresize() {
          const svg = document.querySelector(".preview .image svg")
          if ((svg) && (svg.getAttribute("height") == 99999)) {
            const height = svg.querySelector("#metrics-end")?.getBoundingClientRect()?.y - svg.getBoundingClientRect()?.y
            if (Number.isFinite(height))
              svg.setAttribute("height", height)
          }
        },
        //Generate metrics and flush cache
        async generate() {
          //Avoid requests spamming
          if (this.generated.pending)
            return
          this.generated.pending = true
          //Compute metrics
          try {
            await axios.get(`/.uncache?&token=${(await axios.get(`/.uncache?user=${this.user}`)).data.token}`)
            this.generated.content = (await axios.get(this.url)).data
            this.generated.error = null
          }
          catch (error) {
            this.generated.error = { code: error.response.status, message: error.response.data }
          }
          finally {
            this.generated.pending = false
          }
        },
      },
    })
  })()
  