import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
import {
  findClassBindingInScope,
  findEnclosingClassDef,
} from '../../scope-resolution/scope/walkers.js';
import { SupportedLanguages } from 'gitnexus-shared';
import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
import {
  populateClassOwnedMembers,
  tagNamespacePrefixes,
} from '../../scope-resolution/scope/walkers.js';
import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
import { cppProvider } from '../c-cpp.js';
import { cppArityCompatibility } from './arity.js';
import { cppConversionRank } from './conversion-rank.js';
import { cppMergeBindings } from './merge-bindings.js';
import { resolveCppImportTarget } from './import-target.js';
import { scanCppHeaderFiles } from './header-scan.js';
import {
  expandCppWildcardNames,
  isFileLocal,
  clearFileLocalNames,
  populateCppAnonymousNamespaceScopes,
  populateCppNonGloballyVisible,
  isCppDefGloballyVisible,
} from './file-local-linkage.js';
import {
  populateCppDependentBases,
  clearCppDependentBases,
  isCppDependentBaseMember,
} from './two-phase-lookup.js';
import { populateCppAssociatedNamespaces, clearCppAdlState, pickCppAdlCandidates } from './adl.js';
import { applyCppCaptureSideChannel } from './capture-side-channel.js';
import {
  clearCppInlineNamespaces,
  populateCppInlineNamespaceScopes,
  resolveCppQualifiedNamespaceMember,
} from './inline-namespaces.js';
import { populateCppRangeBindings } from './range-bindings.js';
import { cppConstraintCompatibility } from './constraint-filter.js';
import {
  clearCppUserDefinedConversions,
  populateCppUserDefinedConversions,
} from './user-defined-conversions.js';

/**
 * Per-pass memo of the augmented `#include`-resolution file set
 * (`allFilePaths` ∪ header paths), keyed on the two stable source sets.
 * `resolveImportTarget` is called once per C++ `#include`; the old code rebuilt
 * a fresh ~F-entry `Set` on every call AND defeated the shared
 * `resolveCImportTarget` suffix-index memo (in `c/import-target.ts`) by handing
 * it a new set identity each time. Both inputs are stable per pass, so the
 * union is built once and reused. `WeakMap`-keyed → reclaimed with the pass.
 * (Twin of the C resolver's `augmentedFilePaths`.)
 */
const augmentedPathsByPass = new WeakMap<
  ReadonlySet<string>,
  WeakMap<ReadonlySet<string>, ReadonlySet<string>>
>();

function augmentedFilePaths(
  allFilePaths: ReadonlySet<string>,
  headerPaths: ReadonlySet<string>,
): ReadonlySet<string> {
  let byHeaders = augmentedPathsByPass.get(allFilePaths);
  if (byHeaders === undefined) {
    byHeaders = new WeakMap();
    augmentedPathsByPass.set(allFilePaths, byHeaders);
  }
  let augmented = byHeaders.get(headerPaths);
  if (augmented === undefined) {
    const set = new Set(allFilePaths);
    for (const h of headerPaths) set.add(h);
    augmented = set;
    byHeaders.set(headerPaths, augmented);
  }
  return augmented;
}

/**
 * C++ `ScopeResolver` registered in `SCOPE_RESOLVERS` and consumed by
 * the generic `runScopeResolution` orchestrator (RFC #909 Ring 3).
 *
 * C++ extends C's scope resolution with:
 *   - Namespaces (`namespace foo { ... }`)
 *   - Classes with methods and multiple inheritance
 *   - `using namespace` (wildcard import from namespace)
 *   - `using X::name` (named import from namespace)
 *   - Anonymous namespace (file-local linkage, like C `static`)
 *   - Default parameters (requiredParameterCount < parameterCount)
 *   - Overloading (arity-based disambiguation)
 *   - Templates (V1: generic-ignored, `List<User>` ≡ `List`)
 *   - Leftmost-base MRO for multiple inheritance
 */
export const cppScopeResolver: ScopeResolver = {
  language: SupportedLanguages.CPlusPlus,
  languageProvider: cppProvider,
  importEdgeReason: 'cpp-scope: include',

  loadResolutionConfig: (repoPath: string) => {
    // Clear stale per-pipeline state from any previous invocation.
    clearFileLocalNames();
    clearCppDependentBases();
    clearCppAdlState();
    clearCppInlineNamespaces();
    clearCppUserDefinedConversions();
    return scanCppHeaderFiles(repoPath);
  },

  resolveImportTarget: (targetRaw, fromFile, allFilePaths, resolutionConfig) => {
    // Augment allFilePaths with header files discovered via loadResolutionConfig.
    // C++ .h/.hpp/.hxx/.hh files may be classified differently by language
    // detection but are importable from .cpp files via #include.
    const headerPaths = resolutionConfig as ReadonlySet<string> | undefined;
    if (headerPaths !== undefined && headerPaths.size > 0) {
      return resolveCppImportTarget(
        targetRaw,
        fromFile,
        augmentedFilePaths(allFilePaths, headerPaths),
      );
    }
    return resolveCppImportTarget(targetRaw, fromFile, allFilePaths);
  },

  expandsWildcardTo: (targetModuleScope, parsedFiles) =>
    expandCppWildcardNames(targetModuleScope, parsedFiles),

  mergeBindings: (existing, incoming, scopeId) => cppMergeBindings(existing, incoming, scopeId),

  // Adapter: cppArityCompatibility predates ScopeResolver and uses
  // (def, callsite). ScopeResolver contract is (callsite, def).
  arityCompatibility: (callsite, def) => cppArityCompatibility(def, callsite),

  // SFINAE / `requires`-clause aware overload filter (issue #1579).
  // Drops candidates whose template constraints (`enable_if_t<P, T>`,
  // C++20 `requires P`) provably fail at the call site. Three-valued —
  // `'unknown'` keeps the candidate, preserving "degrade not lie".
  constraintCompatibility: cppConstraintCompatibility,

  buildMro: (graph, parsedFiles, nodeLookup) =>
    buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),

  // Worker-boundary restore (see `ScopeResolver.applyCaptureSideChannel`).
  // `emitCppScopeCaptures` records per-file ADL call-site arg shapes
  // (`markCppAdlSiteArgs`/`markCppAdlSiteNoAdl`), inline-/anonymous-namespace
  // ranges (`markCppInlineNamespaceRange`/`markCppAnonymousNamespaceRange`),
  // dependent-base names (`markCppDependentBase`/`markCppDependentPackBase`),
  // and file-local linkage (`markFileLocal`) into module-level maps as a SIDE
  // EFFECT — none of it is serialized onto the returned ParsedFile's scopes/defs.
  // On the worker path those marks are populated in the worker process and lost
  // across the MessageChannel / disk store; the main thread reuses the
  // serialized ParsedFile and skips `extractParsedFile`, so `populateOwners` +
  // the ADL / two-phase-lookup passes would see empty maps and emit zero edges.
  // The worker stashed a plain-data snapshot on `parsed.captureSideChannel` via
  // `cppProvider.collectCaptureSideChannel`; this restores it into the module
  // maps WITHOUT any tree-sitter re-parse (the #1983 fix — the old re-parse
  // replay re-OOM'd huge `.h`/`.cpp` repos). The freshly-extracted leg never
  // calls this — its marks were just populated in this process.
  applyCaptureSideChannel: applyCppCaptureSideChannel,

  populateOwners: (parsed: ParsedFile) => {
    populateClassOwnedMembers(parsed);
    // #1982: tag namespace-nested defs with their enclosing-namespace prefix so
    // resolveDefGraphId can map them to the namespace-qualified structure-phase
    // node (`NS.A.Inner`) instead of collapsing same-tail nested bases via the
    // simpleKey fallback. Does NOT change qualifiedName (resolution unaffected).
    tagNamespacePrefixes(parsed);
    // Resolve inline- and anonymous-namespace ranges (recorded at capture
    // time) to ScopeIds BEFORE `populateCppNonGloballyVisible` runs, so
    // both exemptions see the populated Sets.
    populateCppInlineNamespaceScopes(parsed);
    populateCppAnonymousNamespaceScopes(parsed);
    // Track namespace-nested and class-nested defs so the global free-call
    // fallback and wildcard expansion can suppress them as unqualified
    // cross-file callables.
    populateCppNonGloballyVisible(parsed);
    // Build the class-def → enclosing-namespace-qualified-name map used
    // by ADL (U2 of plan 2026-05-13-001) to identify each argument type's
    // associated namespace for Koenig lookup.
    populateCppAssociatedNamespaces(parsed);
    // Build conservative one-step user-defined conversion facts for
    // overload ranking (#1631): implicit converting constructors only,
    // with no chaining or conversion-operator handling.
    populateCppUserDefinedConversions(parsed);
  },

  // Resolve recorded template-class → dependent-base simple names to
  // class nodeIds for two-phase template lookup (U3 of plan
  // 2026-05-13-001). Runs AFTER all files have had `populateOwners`
  // applied so that cross-file base classes (e.g. Base in base.h,
  // Derived in derived.h) are reachable in the workspace index.
  populateWorkspaceOwners: (parsedFiles: readonly ParsedFile[]) => {
    populateCppDependentBases(parsedFiles);
  },

  // Simple `isSuperReceiver` returns false for C++. Real super
  // classification is caller-context-dependent and lives in
  // `isSuperReceiverInContext` below — without scope context the
  // previous regex `/^[A-Z]\w*::/` misclassified namespace-qualified
  // calls (e.g., `Singleton::getInstance()`) as super calls and routed
  // them through the wrong resolution branch.
  isSuperReceiver: () => false,

  isSuperReceiverInContext: (text, callerScope, scopes) => {
    // The receiver text comes from the LHS of `::` in `qualified_identifier`
    // (e.g., for `Base<T>::method()`, text is `Base<T>`). Strip template
    // arguments (V1: name-only matching, generics ignored) and any leading
    // namespace qualifier so the lookup matches the bare class def's
    // simple name. `Base<T>::method()` → `Base`; `outer::v1::Base<T>` →
    // `Base`. This handles the Phase 5 cross-unit composition where
    // qualified base-method calls appear inside template bodies.
    let lhs = text;
    const sepIdx = lhs.indexOf('::');
    if (sepIdx > 0) lhs = lhs.slice(0, sepIdx).trim();
    // Strip trailing template-argument list (greedy: drop everything from
    // the first `<` onward — V1 ignores generics).
    const lt = lhs.indexOf('<');
    if (lt > 0) lhs = lhs.slice(0, lt).trim();
    // Strip nested namespace prefix from the receiver text itself (the
    // `outer::v1::Base` shape that appears in derived-list `base_class_clause`).
    const lastDoubleColon = lhs.lastIndexOf('::');
    if (lastDoubleColon >= 0) lhs = lhs.slice(lastDoubleColon + 2).trim();
    if (lhs.length === 0) return false;

    // Resolve the LHS in the caller's scope chain. Only class-like
    // resolutions can be super receivers; Namespace and unresolved
    // names are not super calls.
    const lhsDef = findClassBindingInScope(callerScope, lhs, scopes);
    if (lhsDef === undefined) return false;

    // The caller must have an enclosing class — super calls only make
    // sense inside a class body. Free functions can use `ClassName::`
    // for namespace-qualified calls but those are not super.
    const enclosing = findEnclosingClassDef(callerScope, scopes);
    if (enclosing === undefined) return false;

    // `lhsDef` must be in the caller's MRO (i.e., the caller's enclosing
    // class derives from it). The class itself counts as its own MRO
    // root — `Self::method()` is a qualified self-call, not a super
    // call, so exclude the caller's own class.
    if (lhsDef.nodeId === enclosing.nodeId) return false;
    const mro = scopes.methodDispatch.mroFor(enclosing.nodeId);
    return mro.includes(lhsDef.nodeId);
  },

  // C++ is statically typed — disable field fallback heuristic
  fieldFallbackOnMethodLookup: false,
  // C++ needs return type propagation across #include boundaries
  propagatesReturnTypesAcrossImports: true,
  // C++ #include brings in all symbols — enable global free call fallback
  allowGlobalFreeCallFallback: true,
  // C++ standard-conversion-sequence ranking for overload resolution (#1578).
  // Disambiguates `f(int)` vs `f(double)` called with `f(2.5)` by scoring
  // each candidate's conversion cost; exact match wins over standard conversion.
  conversionRankFn: cppConversionRank,
  // Range-for element type inference: for (auto& user : users) → bind user to User
  populateRangeBindings: populateCppRangeBindings,
  // C++ method return-type bindings need to be visible from module scope
  // for cross-file propagation and compound-receiver chain resolution.
  // cppBindingScopeFor hoists @type-binding.return to Module scope.
  hoistTypeBindingsToModule: true,
  // Enable receiver-bound explicit-`this` fallback only for C++.
  resolveThisViaEnclosingClass: true,
  // The `isFileLocalDef` hook on the global free-call fallback names
  // file-local linkage historically, but semantically gates "logically
  // invisible cross-file" defs. C++ extends this to also reject class-
  // owned methods/fields and namespace-nested symbols — an unqualified
  // call from a free function MUST NOT resolve to `User::save` or
  // `ns::foo` (Cppreference, "Unqualified name lookup"). Without this
  // gate, the global fallback walks every callable in the workspace
  // registry and matches any class method or namespace function by
  // simple name.
  isFileLocalDef: (def: SymbolDefinition) => {
    const simple = def.qualifiedName?.split('.').pop() ?? def.qualifiedName ?? '';
    if (isFileLocal(def.filePath, simple)) return true;
    // Class-owned (Method/Field) — `populateClassOwnedMembers` already
    // stamps `ownerId`; cheap fast-path before consulting the scope map.
    if (def.ownerId !== undefined) return true;
    // Namespace-nested defs — require qualification cross-file. Scope-
    // walked at `populateOwners` time into a per-file nodeId set.
    if (!isCppDefGloballyVisible(def.filePath, def.nodeId)) return true;
    return false;
  },

  // C++ two-phase template lookup: inside a class template body,
  // unqualified calls MUST NOT bind to members of a dependent base
  // class. The standard requires `this->name()` or `Base<T>::name()`
  // forms to make the lookup dependent. Without this gate the global
  // free-call fallback walks the workspace registry and silently binds
  // unqualified calls to dependent-base members, producing CALLS edges
  // the compiler would reject. See plan 2026-05-13-001 U3.
  isCallableVisibleFromCaller: ({ candidate, callerScope, scopes }) => {
    if (callerScope === undefined || scopes === undefined) return true;
    // Reject when the candidate is a member of a dependent base of the
    // caller's enclosing template class. Otherwise allow.
    return !isCppDependentBaseMember(callerScope, candidate, scopes);
  },

  // C++ argument-dependent / Koenig lookup (U2 of plan 2026-05-13-001).
  // Contributes candidates from associated namespaces of class-typed
  // arguments; caller merges with ordinary unqualified lookup candidates.
  // Current boundary: class-typed value/pointer/reference args and template
  // specializations with explicit type arguments contribute associated
  // namespaces. Function-pointer args and full conversion-ranking remain
  // excluded.
  resolveAdlCandidates: (site, callerParsed, scopes, parsedFiles) => {
    // `using ns::name;` introduces `name` into ordinary unqualified lookup.
    // For template-class method bodies, lexical scope walks can miss this
    // named-using visibility; recover by resolving the imported namespace
    // member directly when the local call name matches a named using import.
    const usingNamedHits: SymbolDefinition[] = [];
    const seenUsing = new Set<string>();
    for (const imp of callerParsed.parsedImports) {
      if (imp.kind !== 'named') continue;
      if (imp.localName !== site.name) continue;
      const member = resolveCppQualifiedNamespaceMember(
        imp.targetRaw,
        imp.importedName,
        parsedFiles,
        scopes,
      );
      if (member === undefined || member === 'ambiguous') continue;
      if (seenUsing.has(member.nodeId)) continue;
      seenUsing.add(member.nodeId);
      usingNamedHits.push(member);
    }
    const adlHits = pickCppAdlCandidates(site, callerParsed, scopes, parsedFiles);
    if (usingNamedHits.length === 0) return adlHits;
    if (adlHits === undefined || adlHits.length === 0) return usingNamedHits;
    const merged: SymbolDefinition[] = [];
    const seen = new Set<string>();
    for (const hit of usingNamedHits) {
      seen.add(hit.nodeId);
      merged.push(hit);
    }
    for (const hit of adlHits) {
      if (seen.has(hit.nodeId)) continue;
      seen.add(hit.nodeId);
      merged.push(hit);
    }
    return merged;
  },

  // C++ qualified namespace-member resolution (U5 of plan 2026-05-13-001).
  // Handles `outer::foo()` where `outer` is a namespace (not a class).
  // Walks each parsed file's namespace scopes by simple name, then
  // descends transitively through inline-namespace children when
  // searching for the called member. Returns undefined for non-namespace
  // receivers so receiver-bound-calls Case 2 still gets a chance.
  resolveQualifiedReceiverMember: (
    receiverName,
    memberName,
    _callerScope,
    scopes,
    parsedFiles,
    callsite,
  ) => resolveCppQualifiedNamespaceMember(receiverName, memberName, parsedFiles, scopes, callsite),
};
