import { MODULE_ID } from '../../../constants.js';
import { ActionQualifier } from '../../../rule-elements/operations/ActionQualifier.js';
import { getVisibilityBetween } from '../../../utils.js';
// Debug logger removed
import { isTokenWithinTemplate, shouldFilterAlly } from './shared-utils.js';

function getWallCenter(wall) {
  if (!wall) return null;
  if (Number.isFinite(wall.center?.x) && Number.isFinite(wall.center?.y)) return wall.center;
  const doc = wall.document ?? wall;
  const c = Array.isArray(doc?.c) ? doc.c : null;
  if (c && c.length >= 4) return { x: (c[0] + c[2]) / 2, y: (c[1] + c[3]) / 2 };
  if (
    Number.isFinite(doc?.x) &&
    Number.isFinite(doc?.y) &&
    Number.isFinite(doc?.x2) &&
    Number.isFinite(doc?.y2)
  ) {
    return { x: (doc.x + doc.x2) / 2, y: (doc.y + doc.y2) / 2 };
  }
  return null;
}

function memoize(fn) {
  let hasValue = false;
  let value;
  return () => {
    if (!hasValue) {
      value = fn();
      hasValue = true;
    }
    return value;
  };
}

function getSubjectId(subject) {
  return subject?.document?.id ?? subject?.id ?? subject?.actor?.id ?? null;
}

function createCachedVisibilityReader() {
  const cache = new Map();
  return (observer, target) => {
    const observerId = getSubjectId(observer);
    const targetId = getSubjectId(target);
    if (!observerId || !targetId) return getVisibilityBetween(observer, target);

    const key = `${observerId}->${targetId}`;
    if (!cache.has(key)) {
      cache.set(key, getVisibilityBetween(observer, target));
    }
    return cache.get(key);
  };
}

function createTargetCheckContext(actionData) {
  return {
    getVisibilityBetween: createCachedVisibilityReader(),
    getSeekerPerceptionRank: memoize(() => {
      const stat = actionData.actor?.actor?.getStatistic?.('perception');
      return Number(stat?.proficiency?.rank ?? stat?.rank ?? 0);
    }),
    getActorRollOptions: memoize(() => {
      const rollOptions = actionData.actor?.actor?.getRollOptions?.();
      return Array.isArray(rollOptions) ? rollOptions : [];
    }),
    actorIsConcealed: memoize(() => {
      const itemTypeConditions = actionData.actor?.actor?.itemTypes?.condition || [];
      const legacyConditions = actionData.actor?.actor?.conditions?.conditions || [];
      return (
        itemTypeConditions.some((c) => c?.slug === 'concealed') ||
        legacyConditions.some((c) => c?.slug === 'concealed')
      );
    }),
  };
}

function seekIgnoresConcealment(actionData, target) {
  try {
    return ActionQualifier.ignoreConcealment(actionData.actor, 'seek', target);
  } catch {
    return false;
  }
}

export function checkForValidTargets(actionData) {
  // Guard: canvas or tokens not ready yet in early hook timings
  const tokenLayer = canvas?.tokens;
  const allTokens = tokenLayer?.placeables || [];
  if (!tokenLayer || !Array.isArray(allTokens)) return false;

  const potentialTargets = allTokens.filter((token) => {
    if (token === actionData.actor) return false;
    if (!token.actor) return false;
    const actorType = token.actor.type;
    if (actionData.actionType === 'seek' && actorType === 'loot') return true;
    if (actorType !== 'character' && actorType !== 'npc' && actorType !== 'hazard') return false;
    return true;
  });
  const checkContext = createTargetCheckContext(actionData);

  switch (actionData.actionType) {
    case 'consequences':
      if (potentialTargets.length === 0) return false;
      return checkConsequencesTargets(actionData, potentialTargets, checkContext);
    case 'seek':
      return checkSeekTargets(actionData, potentialTargets, checkContext);
    case 'point-out':
      if (potentialTargets.length === 0) return false;
      return checkPointOutTargets(actionData, potentialTargets);
    case 'hide':
      if (potentialTargets.length === 0) return false;
      return checkHideTargets(actionData, potentialTargets);
    case 'sneak':
      if (potentialTargets.length === 0) return false;
      return checkSneakTargets(actionData, potentialTargets);
    case 'create-a-diversion':
      if (potentialTargets.length === 0) return false;
      return checkDiversionTargets(actionData, potentialTargets);
    default:
      return true;
  }
}

function checkConsequencesTargets(actionData, potentialTargets, checkContext) {
  for (const target of potentialTargets) {
    if (shouldFilterAlly(actionData.actor, target, 'enemies', actionData?.ignoreAllies)) {
      continue;
    }
    let visibility = checkContext.getVisibilityBetween(target, actionData.actor);

    try {
      if (visibility === 'observed' && checkContext.actorIsConcealed()) {
        visibility = 'concealed';
      }
    } catch (error) {
      console.error('Error checking concealment in checkConsequencesTargets:', error);
    }

    if (visibility === 'hidden' || visibility === 'undetected') {
      return true;
    }
  }
  return false;
}

function checkSeekTargets(actionData, potentialTargets, checkContext) {
  try {
    const scene = canvas?.scene;
    if (scene) {
      const hasTemplate = actionData.seekTemplateCenter && actionData.seekTemplateRadiusFeet;

      const allWalls = canvas?.walls?.placeables || [];
      const hiddenWalls = allWalls.filter((w) => !!w?.document?.getFlag?.(MODULE_ID, 'hiddenWall'));

      if (hiddenWalls.length > 0) {
        if (hasTemplate) {
          const wallsInTemplate = hiddenWalls.some((wall) => {
            try {
              const wallCenter = getWallCenter(wall);
              if (wallCenter && canvas.scene?.grid?.size) {
                const distance = Math.sqrt(
                  Math.pow(wallCenter.x - actionData.seekTemplateCenter.x, 2) +
                    Math.pow(wallCenter.y - actionData.seekTemplateCenter.y, 2),
                );
                const gridDistance = canvas.scene.grid.distance ?? canvas.grid?.distance ?? 5;
                const radiusPixels =
                  (actionData.seekTemplateRadiusFeet * canvas.scene.grid.size) / gridDistance;
                return distance <= radiusPixels;
              }
              return false;
            } catch {
              return false;
            }
          });
          if (wallsInTemplate) return true;
        } else {
          return true;
        }
      }

      const allSceneTokens = canvas.tokens?.placeables || [];
      const lootTokens = allSceneTokens.filter(
        (token) =>
          token !== actionData.actor &&
          !token.actor &&
          (token.document?.getFlag?.(MODULE_ID, 'isLoot') ||
            token.document?.getFlag?.(MODULE_ID, 'minPerceptionRank')),
      );

      if (lootTokens.length > 0) {
        const validLootTokens = hasTemplate
          ? lootTokens.filter((token) =>
              isTokenWithinTemplate(
                actionData.seekTemplateCenter,
                actionData.seekTemplateRadiusFeet,
                token,
              ),
            )
          : lootTokens;

        if (validLootTokens.length > 0) {
          for (const lootToken of validLootTokens) {
            const minRank = Number(
              lootToken.document?.getFlag?.(MODULE_ID, 'minPerceptionRank') ?? 0,
            );
            if (Number.isFinite(minRank) && minRank > 0) {
              const seekerRank = checkContext.getSeekerPerceptionRank();
              if (Number.isFinite(seekerRank) && seekerRank >= minRank) {
                return true;
              }
            } else {
              return true;
            }
          }
        }
      }
    }
  } catch (_) {}

  const hasHiddenOrUndetectedRollOption = memoize(() =>
    checkContext
      .getActorRollOptions()
      .some((opt) => opt.includes('target:hidden') || opt.includes('target:undetected')),
  );
  for (const target of potentialTargets) {
    // Check if target is a hazard/loot with a minimum perception rank
    try {
      if (target?.actor && (target.actor.type === 'hazard' || target.actor.type === 'loot')) {
        const minRank = Number(target.document?.getFlag?.(MODULE_ID, 'minPerceptionRank') ?? 0);
        if (Number.isFinite(minRank) && minRank > 0) {
          const seekerRank = checkContext.getSeekerPerceptionRank();
          if (!(Number.isFinite(seekerRank) && seekerRank >= minRank)) {
            // Not enough proficiency: indicate special row action state and skip as a valid seek target
            actionData._visionerSeekProficiencyBlocked = true;
            continue;
          }
        }
      }

      // Handle loot tokens and hidden walls that don't have actors
      if (!target.actor) {
        // Check if this is a loot token or hidden wall by looking at the token's properties
        const isLootOrHiddenWall =
          target.document?.getFlag?.(MODULE_ID, 'isLoot') ||
          target.document?.getFlag?.(MODULE_ID, 'isHiddenWall') ||
          target.document?.getFlag?.(MODULE_ID, 'minPerceptionRank');

        if (isLootOrHiddenWall) {
          // Check perception rank requirement if set
          const minRank = Number(target.document?.getFlag?.(MODULE_ID, 'minPerceptionRank') ?? 0);
          if (Number.isFinite(minRank) && minRank > 0) {
            const seekerRank = checkContext.getSeekerPerceptionRank();
            if (!(Number.isFinite(seekerRank) && seekerRank >= minRank)) {
              // Not enough proficiency: indicate special row action state and skip as a valid seek target
              actionData._visionerSeekProficiencyBlocked = true;
              continue;
            }
          }
          // If no rank requirement or requirement met, this is a valid seek target
          return true;
        }
      }
    } catch (_) {}

    const visibility = checkContext.getVisibilityBetween(actionData.actor, target);
    if (['hidden', 'undetected'].includes(visibility)) return true;
    if (visibility === 'concealed' && seekIgnoresConcealment(actionData, target)) return true;
    if (target.actor) {
      const conditions = target.actor.conditions?.conditions || [];
      const isHiddenOrUndetected = conditions.some((c) =>
        ['hidden', 'undetected'].includes(c.slug),
      );
      if (isHiddenOrUndetected) return true;
    }
    if (hasHiddenOrUndetectedRollOption()) return true;
  }
  return false;
}

function checkPointOutTargets(actionData, potentialTargets) {
  let hasAlly = false;
  let hasValidTarget = false;
  for (const token of potentialTargets) {
    if (!hasAlly && token.document.disposition === actionData.actor.document.disposition)
      hasAlly = true;
  }
  for (const token of potentialTargets) {
    if (token.document.disposition !== actionData.actor.document.disposition) {
      hasValidTarget = true;
      break;
    }
  }
  return hasAlly && hasValidTarget;
}

function checkHideTargets(actionData, potentialTargets) {
  return potentialTargets.length > 0;
}

function checkSneakTargets(actionData, potentialTargets) {
  return potentialTargets.length > 0;
}

function checkDiversionTargets(actionData, potentialTargets) {
  const { discoverDiversionObservers } = game.pf2eVisionerCache?.createADiversion || {};
  try {
    if (discoverDiversionObservers) {
      const observers = discoverDiversionObservers(actionData.actor);
      return observers.length > 0;
    }
  } catch (_) {}
  // Fallback to simple heuristic if dynamic import not cached
  return potentialTargets.length > 0;
}
