import test from 'node:test';
import assert from 'node:assert/strict';

import type { AnkiConnectConfig } from '../types/anki';
import {
  DEFAULT_MATURE_INTERVAL_THRESHOLD_DAYS,
  buildKnownWordMaturityTierQueries,
  classifyKnownWordNoteTier,
  getKnownWordMaturityEnabled,
  getMatureIntervalThresholdDays,
  maxKnownWordMaturityTier,
  sanitizeKnownWordMaturityTier,
} from './known-word-maturity';

function makeConfig(knownWords: AnkiConnectConfig['knownWords']): AnkiConnectConfig {
  return { url: 'http://127.0.0.1:8765', knownWords } as AnkiConnectConfig;
}

test('maturity is enabled only when both highlight and maturity flags are on', () => {
  assert.equal(
    getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true, maturityEnabled: true })),
    true,
  );
  assert.equal(
    getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: false, maturityEnabled: true })),
    false,
  );
  assert.equal(
    getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true, maturityEnabled: false })),
    false,
  );
  assert.equal(getKnownWordMaturityEnabled(makeConfig({ highlightEnabled: true })), false);
  assert.equal(getKnownWordMaturityEnabled(makeConfig(undefined)), false);
});

test('mature threshold falls back to default for invalid values', () => {
  assert.equal(DEFAULT_MATURE_INTERVAL_THRESHOLD_DAYS, 21);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 30 })), 30);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 14.9 })), 14);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: 0 })), 21);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: -5 })), 21);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({ matureThresholdDays: Number.NaN })), 21);
  assert.equal(getMatureIntervalThresholdDays(makeConfig({})), 21);
  assert.equal(getMatureIntervalThresholdDays(makeConfig(undefined)), 21);
});

test('tier queries append Anki search props to a deck scope query', () => {
  const queries = buildKnownWordMaturityTierQueries('deck:"Mining"', 21);
  assert.equal(queries.mature, 'deck:"Mining" prop:ivl>=21 -is:learn');
  assert.equal(queries.young, 'deck:"Mining" prop:ivl>=1 prop:ivl<21 -is:learn');
  assert.equal(queries.learning, 'deck:"Mining" is:learn');
});

test('interval tiers exclude (re)learning cards so the buckets stay disjoint', () => {
  const queries = buildKnownWordMaturityTierQueries('deck:"Mining"', 21);
  // A lapsed card keeps an interval of at least the lapse minInt (>= 1), so
  // without the exclusion the young query would claim every relearning card
  // and the learning tier could only ever match brand-new cards mid-step.
  for (const intervalQuery of [queries.mature, queries.young]) {
    assert.ok(intervalQuery.includes('-is:learn'));
  }
  assert.equal(queries.learning, 'deck:"Mining" is:learn');
});

test('tier queries with an empty scope query have no leading space', () => {
  const queries = buildKnownWordMaturityTierQueries('', 30);
  assert.equal(queries.mature, 'prop:ivl>=30 -is:learn');
  assert.equal(queries.young, 'prop:ivl>=1 prop:ivl<30 -is:learn');
  assert.equal(queries.learning, 'is:learn');
});

test('note classification picks the most mature matching tier', () => {
  const sets = {
    mature: new Set([1, 4]),
    young: new Set([2, 4]),
    learning: new Set([3, 4, 2]),
  };
  assert.equal(classifyKnownWordNoteTier(1, sets), 'mature');
  assert.equal(classifyKnownWordNoteTier(2, sets), 'young');
  assert.equal(classifyKnownWordNoteTier(3, sets), 'learning');
  // Note with mature, young, and learning cards: most mature card wins.
  assert.equal(classifyKnownWordNoteTier(4, sets), 'mature');
  assert.equal(classifyKnownWordNoteTier(99, sets), 'new');
});

test('maxKnownWordMaturityTier picks the higher tier and tolerates null', () => {
  assert.equal(maxKnownWordMaturityTier('mature', 'new'), 'mature');
  assert.equal(maxKnownWordMaturityTier('learning', 'young'), 'young');
  assert.equal(maxKnownWordMaturityTier('new', null), 'new');
  assert.equal(maxKnownWordMaturityTier(null, 'learning'), 'learning');
  assert.equal(maxKnownWordMaturityTier(null, null), null);
  assert.equal(maxKnownWordMaturityTier(undefined, undefined), null);
});

test('sanitizeKnownWordMaturityTier accepts only valid tiers', () => {
  assert.equal(sanitizeKnownWordMaturityTier('mature'), 'mature');
  assert.equal(sanitizeKnownWordMaturityTier('young'), 'young');
  assert.equal(sanitizeKnownWordMaturityTier('learning'), 'learning');
  assert.equal(sanitizeKnownWordMaturityTier('new'), 'new');
  assert.equal(sanitizeKnownWordMaturityTier('MATURE'), null);
  assert.equal(sanitizeKnownWordMaturityTier(''), null);
  assert.equal(sanitizeKnownWordMaturityTier(21), null);
  assert.equal(sanitizeKnownWordMaturityTier(null), null);
  assert.equal(sanitizeKnownWordMaturityTier(undefined), null);
});
