import { withLeadingSlash } from 'ufo';

function createRendererContext({ manifest, precomputed, buildAssetsURL }) {
  if (!manifest && !precomputed) {
    throw new Error("Either manifest or precomputed data must be provided");
  }
  const ctx = {
    // Options
    buildAssetsURL: buildAssetsURL || withLeadingSlash,
    manifest,
    precomputed,
    updateManifest,
    // Internal cache
    _dependencies: {},
    _dependencySets: {},
    _entrypoints: []
  };
  function updateManifest(manifest2) {
    const manifestEntries = Object.entries(manifest2);
    ctx.manifest = manifest2;
    ctx._dependencies = {};
    ctx._dependencySets = {};
    ctx._entrypoints = manifestEntries.filter((e) => e[1].isEntry).map(([module]) => module);
  }
  if (precomputed) {
    ctx._dependencies = precomputed.dependencies;
    ctx._entrypoints = precomputed.entrypoints;
  } else if (manifest) {
    updateManifest(manifest);
  }
  return ctx;
}
function getModuleDependencies(id, rendererContext) {
  if (rendererContext._dependencies[id]) {
    return rendererContext._dependencies[id];
  }
  const dependencies = rendererContext._dependencies[id] = {
    scripts: {},
    styles: {},
    preload: {},
    prefetch: {}
  };
  if (!rendererContext.manifest) {
    return dependencies;
  }
  const meta = rendererContext.manifest[id];
  if (!meta) {
    return dependencies;
  }
  if (meta.file) {
    dependencies.preload[id] = meta;
    if (meta.isEntry || meta.sideEffects) {
      dependencies.scripts[id] = meta;
    }
  }
  for (const css of meta.css || []) {
    dependencies.styles[css] = dependencies.preload[css] = dependencies.prefetch[css] = rendererContext.manifest[css];
  }
  for (const asset of meta.assets || []) {
    dependencies.preload[asset] = dependencies.prefetch[asset] = rendererContext.manifest[asset];
  }
  for (const depId of meta.imports || []) {
    const depDeps = getModuleDependencies(depId, rendererContext);
    for (const key in depDeps.styles) {
      dependencies.styles[key] = depDeps.styles[key];
    }
    for (const key in depDeps.preload) {
      dependencies.preload[key] = depDeps.preload[key];
    }
    for (const key in depDeps.prefetch) {
      dependencies.prefetch[key] = depDeps.prefetch[key];
    }
  }
  const filteredPreload = {};
  for (const id2 in dependencies.preload) {
    const dep = dependencies.preload[id2];
    if (dep.preload) {
      filteredPreload[id2] = dep;
    }
  }
  dependencies.preload = filteredPreload;
  return dependencies;
}
function getAllDependencies(ids, rendererContext) {
  let cacheKey = "";
  const sortedIds = [...ids].sort();
  for (let i = 0; i < sortedIds.length; i++) {
    if (i > 0) cacheKey += ",";
    cacheKey += sortedIds[i];
  }
  if (rendererContext._dependencySets[cacheKey]) {
    return rendererContext._dependencySets[cacheKey];
  }
  const allDeps = {
    scripts: {},
    styles: {},
    preload: {},
    prefetch: {}
  };
  for (const id of ids) {
    const deps = getModuleDependencies(id, rendererContext);
    for (const key in deps.scripts) {
      allDeps.scripts[key] = deps.scripts[key];
    }
    for (const key in deps.styles) {
      allDeps.styles[key] = deps.styles[key];
    }
    for (const key in deps.preload) {
      allDeps.preload[key] = deps.preload[key];
    }
    for (const key in deps.prefetch) {
      allDeps.prefetch[key] = deps.prefetch[key];
    }
    for (const dynamicDepId of rendererContext.manifest?.[id]?.dynamicImports || []) {
      const dynamicDeps = getModuleDependencies(dynamicDepId, rendererContext);
      for (const key in dynamicDeps.scripts) {
        allDeps.prefetch[key] = dynamicDeps.scripts[key];
      }
      for (const key in dynamicDeps.styles) {
        allDeps.prefetch[key] = dynamicDeps.styles[key];
      }
      for (const key in dynamicDeps.preload) {
        allDeps.prefetch[key] = dynamicDeps.preload[key];
      }
    }
  }
  const filteredPrefetch = {};
  for (const id in allDeps.prefetch) {
    const dep = allDeps.prefetch[id];
    if (dep.prefetch) {
      filteredPrefetch[id] = dep;
    }
  }
  allDeps.prefetch = filteredPrefetch;
  for (const id in allDeps.preload) {
    delete allDeps.prefetch[id];
  }
  for (const style in allDeps.styles) {
    delete allDeps.preload[style];
    delete allDeps.prefetch[style];
  }
  rendererContext._dependencySets[cacheKey] = allDeps;
  return allDeps;
}
function getRequestDependencies(ssrContext, rendererContext) {
  if (ssrContext._requestDependencies) {
    return ssrContext._requestDependencies;
  }
  const ids = new Set(Array.from([
    ...rendererContext._entrypoints,
    ...ssrContext.modules || ssrContext._registeredComponents || []
  ]));
  const deps = getAllDependencies(ids, rendererContext);
  ssrContext._requestDependencies = deps;
  return deps;
}
function renderStyles(ssrContext, rendererContext) {
  const { styles } = getRequestDependencies(ssrContext, rendererContext);
  let result = "";
  for (const key in styles) {
    const resource = styles[key];
    result += `<link rel="stylesheet" href="${rendererContext.buildAssetsURL(resource.file)}" crossorigin>`;
  }
  return result;
}
function getResources(ssrContext, rendererContext) {
  return [...getPreloadLinks(ssrContext, rendererContext), ...getPrefetchLinks(ssrContext, rendererContext)];
}
function renderResourceHints(ssrContext, rendererContext) {
  const { preload, prefetch } = getRequestDependencies(ssrContext, rendererContext);
  let result = "";
  for (const key in preload) {
    const resource = preload[key];
    const href = rendererContext.buildAssetsURL(resource.file);
    const rel = resource.module ? "modulepreload" : "preload";
    const crossorigin = resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? " crossorigin" : "";
    if (resource.resourceType && resource.mimeType) {
      result += `<link rel="${rel}" as="${resource.resourceType}" type="${resource.mimeType}"${crossorigin} href="${href}">`;
    } else if (resource.resourceType) {
      result += `<link rel="${rel}" as="${resource.resourceType}"${crossorigin} href="${href}">`;
    } else {
      result += `<link rel="${rel}"${crossorigin} href="${href}">`;
    }
  }
  for (const key in prefetch) {
    const resource = prefetch[key];
    const href = rendererContext.buildAssetsURL(resource.file);
    const crossorigin = resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? " crossorigin" : "";
    if (resource.resourceType && resource.mimeType) {
      result += `<link rel="prefetch" as="${resource.resourceType}" type="${resource.mimeType}"${crossorigin} href="${href}">`;
    } else if (resource.resourceType) {
      result += `<link rel="prefetch" as="${resource.resourceType}"${crossorigin} href="${href}">`;
    } else {
      result += `<link rel="prefetch"${crossorigin} href="${href}">`;
    }
  }
  return result;
}
function renderResourceHeaders(ssrContext, rendererContext) {
  const { preload, prefetch } = getRequestDependencies(ssrContext, rendererContext);
  const links = [];
  for (const key in preload) {
    const resource = preload[key];
    const href = rendererContext.buildAssetsURL(resource.file);
    const rel = resource.module ? "modulepreload" : "preload";
    let header = `<${href}>; rel="${rel}"`;
    if (resource.resourceType) {
      header += `; as="${resource.resourceType}"`;
    }
    if (resource.mimeType) {
      header += `; type="${resource.mimeType}"`;
    }
    if (resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module) {
      header += "; crossorigin";
    }
    links.push(header);
  }
  for (const key in prefetch) {
    const resource = prefetch[key];
    const href = rendererContext.buildAssetsURL(resource.file);
    let header = `<${href}>; rel="prefetch"`;
    if (resource.resourceType) {
      header += `; as="${resource.resourceType}"`;
    }
    if (resource.mimeType) {
      header += `; type="${resource.mimeType}"`;
    }
    if (resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module) {
      header += "; crossorigin";
    }
    links.push(header);
  }
  return {
    link: links.join(", ")
  };
}
function getPreloadLinks(ssrContext, rendererContext) {
  const { preload } = getRequestDependencies(ssrContext, rendererContext);
  const result = [];
  for (const key in preload) {
    const resource = preload[key];
    result.push({
      rel: resource.module ? "modulepreload" : "preload",
      as: resource.resourceType,
      type: resource.mimeType ?? null,
      crossorigin: resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? "" : null,
      href: rendererContext.buildAssetsURL(resource.file)
    });
  }
  return result;
}
function getPrefetchLinks(ssrContext, rendererContext) {
  const { prefetch } = getRequestDependencies(ssrContext, rendererContext);
  const result = [];
  for (const key in prefetch) {
    const resource = prefetch[key];
    result.push({
      rel: "prefetch",
      as: resource.resourceType,
      type: resource.mimeType ?? null,
      crossorigin: resource.resourceType === "style" || resource.resourceType === "font" || resource.resourceType === "script" || resource.module ? "" : null,
      href: rendererContext.buildAssetsURL(resource.file)
    });
  }
  return result;
}
function renderScripts(ssrContext, rendererContext) {
  const { scripts } = getRequestDependencies(ssrContext, rendererContext);
  let result = "";
  for (const key in scripts) {
    const resource = scripts[key];
    if (resource.module) {
      result += `<script type="module" src="${rendererContext.buildAssetsURL(resource.file)}" crossorigin><\/script>`;
    } else {
      result += `<script src="${rendererContext.buildAssetsURL(resource.file)}" defer crossorigin><\/script>`;
    }
  }
  return result;
}
function createRenderer(createApp, renderOptions) {
  const rendererContext = createRendererContext(renderOptions);
  return {
    rendererContext,
    async renderToString(ssrContext) {
      ssrContext._registeredComponents = ssrContext._registeredComponents || /* @__PURE__ */ new Set();
      const _createApp = await Promise.resolve(createApp).then((r) => "default" in r ? r.default : r);
      const app = await _createApp(ssrContext);
      const html = await renderOptions.renderToString(app, ssrContext);
      const wrap = (fn) => () => fn(ssrContext, rendererContext);
      return {
        html,
        renderResourceHeaders: wrap(renderResourceHeaders),
        renderResourceHints: wrap(renderResourceHints),
        renderStyles: wrap(renderStyles),
        renderScripts: wrap(renderScripts)
      };
    }
  };
}

export { createRenderer, createRendererContext, getAllDependencies, getModuleDependencies, getPrefetchLinks, getPreloadLinks, getRequestDependencies, getResources, renderResourceHeaders, renderResourceHints, renderScripts, renderStyles };
