import prettier from 'eslint-config-prettier';
import { fileURLToPath } from 'node:url';
import { includeIgnoreFile } from '@eslint/compat';
import js from '@eslint/js';
import svelte from 'eslint-plugin-svelte';
import boundaries from 'eslint-plugin-boundaries';
import { defineConfig } from 'eslint/config';
import globals from 'globals';
import ts from 'typescript-eslint';
import svelteConfig from './svelte.config.js';

const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));

export default defineConfig(
	includeIgnoreFile(gitignorePath),
	{
		ignores: [
			'src/paraglide/**',
			'.svelte-kit.old/**',
			// Fixture files are intentionally invalid imports used to demonstrate
			// that the boundaries/dependencies rule fires. Exclude them from the
			// regular lint pass so `npm run lint` stays green.
			'src/lib/**/__fixtures__/**'
		]
	},
	js.configs.recommended,
	...ts.configs.recommended,
	...svelte.configs.recommended,
	prettier,
	...svelte.configs.prettier,
	{
		languageOptions: {
			globals: { ...globals.browser, ...globals.node }
		},
		rules: {
			// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
			// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
			'no-undef': 'off',
			// This rule is designed for Svelte 5's own routing system using resolve().
			// In SvelteKit, <a href> and goto() from $app/navigation are the correct patterns — resolve() is not needed.
			'svelte/no-navigation-without-resolve': 'off',
			// Prevents accidental console.log left in source. console.warn and console.error
			// are still permitted for intentional server-side logging (e.g. hooks.server.ts).
			'no-console': ['error', { allow: ['warn', 'error'] }]
		}
	},
	{
		files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
		languageOptions: {
			parserOptions: {
				extraFileExtensions: ['.svelte'],
				parser: ts.parser,
				svelteConfig
			}
		},
		rules: {
			// text-accent resolves to #a1dcd8 in light mode (1.52:1 on white — WCAG fail).
			// layout.css documents it as decorative-only (borders, icon tints, bg fills).
			// For any text label use text-primary or text-ink instead. This rule catches
			// the pattern where text-accent appears inside a JavaScript string literal
			// (e.g. conditional ternary class expressions in Svelte templates).
			'no-restricted-syntax': [
				'error',
				{
					selector: 'Literal[value=/\\btext-accent\\b/]',
					message:
						'text-accent is decorative-only (#a1dcd8 in light mode = 1.52:1 contrast — WCAG fail). Use text-primary or text-ink-2 for text labels.'
				},
				{
					selector: 'TemplateLiteral > TemplateElement[value.raw=/\\btext-accent\\b/]',
					message:
						'text-accent is decorative-only (#a1dcd8 in light mode = 1.52:1 contrast — WCAG fail). Use text-primary or text-ink-2 for text labels.'
				}
			],
			// Primary XSS guard: any {@html ...} block in a Svelte template is a potential
			// injection sink. This rule replaces the regex CI guard's role as the primary
			// defense (the CI regex stays as a backstop). For any legitimate use (e.g.
			// trusted server-rendered Markdown), suppress with an inline
			// `<!-- eslint-disable-next-line svelte/no-at-html-tags -->` and a justification.
			'svelte/no-at-html-tags': 'error',
			// Reverse-tabnabbing (CWE-1022): any `target="_blank"` anchor must carry
			// `rel="noopener noreferrer"`, or the opened page can hijack window.opener.
			// Catches the pattern at lint time instead of relying on review. See #708.
			'svelte/no-target-blank': ['error', { allowReferrer: false, enforceDynamicLinks: 'always' }]
		}
	},
	{
		// E2E tests use console.log for diagnostic output — allow it there.
		files: ['e2e/**'],
		rules: {
			'no-console': 'off'
		}
	},
	{
		files: ['**/*.spec.ts', '**/*.test.ts'],
		rules: {
			'no-restricted-syntax': [
				'error',
				{
					selector:
						"CallExpression[callee.object.name='vi'][callee.property.name='mock'] > Literal[value=/^pdfjs-dist/]",
					message:
						"Banned: vi.mock('pdfjs-dist', factory) causes a birpc teardown race in browser-mode specs — see ADR 012. Use the libLoader prop injection pattern instead."
				},
				{
					// ADR 012 / #553. The named mechanism: an async vi.mock factory whose
					// body performs `await import(...)` produces a late birpc roundtrip
					// during worker teardown. The factory body must be synchronous; if
					// you need to share state between the spec and the mock, use
					// `vi.hoisted` (see DropZone.svelte.spec.ts).
					selector:
						"CallExpression[callee.object.name='vi'][callee.property.name='mock'][arguments.1.type='ArrowFunctionExpression'][arguments.1.async=true]:has(AwaitExpression > ImportExpression)",
					message:
						'Banned: vi.mock(..., async () => { await import(...) }) causes a birpc teardown race in browser-mode specs — see ADR 012. Use a synchronous factory + vi.hoisted instead.'
				}
			]
		}
	},
	{
		// Forbid test fixtures (*.test-fixture.svelte) from being imported by
		// production code. Tree-shaking keeps them out of the production bundle
		// today (no route reaches them), but a lint rule makes the boundary
		// explicit so an accidental autocomplete import in a route or component
		// fails fast. Test files (*.spec.ts / *.test.ts) and the fixtures
		// themselves are exempt — see the next block. Nora #2 on PR #629
		// round 3.
		files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js', '**/*.ts'],
		ignores: ['**/*.spec.ts', '**/*.test.ts', '**/*.test-fixture.svelte'],
		rules: {
			'no-restricted-imports': [
				'error',
				{
					patterns: [
						{
							group: ['**/*.test-fixture.svelte'],
							message:
								'Test fixtures (*.test-fixture.svelte) are test-only — do not import from production code. Tracked by #637.'
						}
					]
				}
			]
		}
	},
	{
		plugins: { boundaries },
		settings: {
			'import/resolver': { typescript: { project: './tsconfig.json' } },
			// $lib/paraglide and $lib/generated are intentionally omitted from the elements list —
			// they are treated as external (third-party-like) by the rule and may be imported
			// from any domain without restriction.
			'boundaries/elements': [
				{ type: 'document', pattern: 'src/lib/document/**' },
				{ type: 'person', pattern: 'src/lib/person/**' },
				{ type: 'tag', pattern: 'src/lib/tag/**' },
				{ type: 'user', pattern: 'src/lib/user/**' },
				{ type: 'geschichte', pattern: 'src/lib/geschichte/**' },
				{ type: 'notification', pattern: 'src/lib/notification/**' },
				{ type: 'ocr', pattern: 'src/lib/ocr/**' },
				{ type: 'activity', pattern: 'src/lib/activity/**' },
				{ type: 'conversation', pattern: 'src/lib/conversation/**' },
				{ type: 'timeline', pattern: 'src/lib/timeline/**' },
				{ type: 'shared', pattern: 'src/lib/shared/**' },
				{ type: 'routes', pattern: 'src/routes/**' }
			]
		},
		rules: {
			'boundaries/dependencies': [
				'error',
				{
					default: 'disallow',
					message:
						"Cross-domain import blocked. Move shared code to $lib/shared/, or expose it via the domain's index.ts.",
					rules: [
						// Document composes person components (D-FE-1) and tag components (D-FE-2),
						// and hosts the OCR trigger in the transcription editor.
						{
							from: { type: 'document' },
							allow: {
								to: { type: ['shared', 'conversation', 'activity', 'person', 'tag', 'ocr'] }
							}
						},
						// Geschichte editor selects persons and documents by design.
						{
							from: { type: 'geschichte' },
							allow: { to: { type: ['shared', 'person', 'document'] } }
						},
						// OCR trigger embeds the document script-type selector.
						{
							from: { type: 'ocr' },
							allow: { to: { type: ['shared', 'document'] } }
						},
						// Activity feed (Chronik) reads notification items for its inbox panel.
						{
							from: { type: 'activity' },
							allow: { to: { type: ['shared', 'notification'] } }
						},
						{ from: { type: 'person' }, allow: { to: { type: ['shared'] } } },
						{ from: { type: 'tag' }, allow: { to: { type: ['shared'] } } },
						{ from: { type: 'user' }, allow: { to: { type: ['shared'] } } },
						{ from: { type: 'notification' }, allow: { to: { type: ['shared'] } } },
						{ from: { type: 'conversation' }, allow: { to: { type: ['shared'] } } },
						// Timeline curator event editor selects persons and documents by
						// design (mirrors the geschichte editor) — #781.
						{
							from: { type: 'timeline' },
							allow: { to: { type: ['shared', 'person', 'document'] } }
						},
						{ from: { type: 'shared' }, allow: { to: { type: ['shared'] } } },
						{
							from: { type: 'routes' },
							allow: {
								to: {
									type: [
										'document',
										'person',
										'tag',
										'user',
										'geschichte',
										'notification',
										'ocr',
										'activity',
										'conversation',
										'timeline',
										'shared'
									]
								}
							}
						}
					]
				}
			]
		}
	}
);
