import { ParameterType } from 'jspsych';

const info = {
  name: 'die-roll',
  parameters: {
    prompt: {
      type: ParameterType.HTML_STRING,
      default: '',
    },
    rigged_value: {
      type: ParameterType.INT,
      default: null,
    },
    roll_duration: {
      type: ParameterType.INT,
      default: 2000,
    },
    animation_interval: {
      type: ParameterType.INT,
      default: 120,
    },
    button_label: {
      type: ParameterType.STRING,
      default: 'Continue',
    },
    result_template: {
      type: ParameterType.STRING,
      default: 'You rolled a {{value}}.',
    },
    practice_trial: {
      type: ParameterType.BOOL,
      default: false,
    },
    practice_roll_limit: {
      type: ParameterType.INT,
      default: 5,
    },
  },
};

class DieRollPlugin {
  constructor(jsPsych) {
    this.jsPsych = jsPsych;
  }

  trial(display_element, trial) {
    const start_time = performance.now();
    const practice_enabled = Boolean(trial.practice_trial);
    const practice_roll_limit = practice_enabled
      ? this.get_practice_roll_limit(trial.practice_roll_limit)
      : 1;
    const sanitized_rigged_value = practice_enabled
      ? null
      : this.get_rigged_value(trial.rigged_value);
    const get_next_result_value = () =>
      sanitized_rigged_value ?? this.get_random_value();
    let pending_final_value = get_next_result_value();
    let last_roll_value = null;

    display_element.innerHTML = this.build_markup(
      trial.prompt,
      trial.button_label
    );

    const face_element = display_element.querySelector(
      '.jspsych_die_roll_face'
    );
    const result_element = display_element.querySelector(
      '.jspsych_die_roll_result'
    );
    const button_element = display_element.querySelector(
      '.jspsych_die_roll_button'
    );

    const PRACTICE_UNLOCK_DELAY_MS = 600;
    const MIN_IDLE_CLICK_INTERVAL_MS = 350;
    let is_rolling = false;
    let has_rolled = false;
    let roll_started_at = null;
    let roll_stopped_at = null;
    let die_click_count = 0;
    let practice_rolls_completed = 0;
    let practice_reenable_timeout = null;
    let previous_face_value = null;
    let last_face_interaction_at = null;

    const clear_timeouts = () => {
      if (practice_enabled && practice_reenable_timeout !== null) {
        window.clearTimeout(practice_reenable_timeout);
        practice_reenable_timeout = null;
      }
      this.jsPsych.pluginAPI.clearAllTimeouts();
    };

    const lock_die_face = () => {
      face_element.classList.remove('jspsych_die_roll_face--interactive');
      face_element.classList.remove('jspsych_die_roll_face--active');
      face_element.classList.add('jspsych_die_roll_face--locked');
      face_element.setAttribute('aria-disabled', 'true');
      face_element.removeAttribute('tabindex');
      face_element.style.pointerEvents = 'none';
    };

    const make_die_interactive = () => {
      face_element.classList.remove('jspsych_die_roll_face--locked');
      face_element.classList.add('jspsych_die_roll_face--interactive');
      face_element.setAttribute('tabindex', '0');
      face_element.removeAttribute('aria-disabled');
      face_element.style.pointerEvents = '';
    };

    const get_next_face_value = () => {
      let value;
      do {
        value = Math.floor(Math.random() * 6) + 1;
      } while (
        value === previous_face_value ||
        (sanitized_rigged_value !== null && value === sanitized_rigged_value)
      );
      previous_face_value = value;
      return value;
    };

    const cycle_face = () => {
      if (!is_rolling) {
        return;
      }
      const value = get_next_face_value();
      face_element.textContent = value;
      this.jsPsych.pluginAPI.setTimeout(cycle_face, trial.animation_interval);
    };

    const finalize_roll = () => {
      if (!is_rolling || has_rolled) {
        return;
      }
      is_rolling = false;
      has_rolled = true;
      die_click_count += 1;
      roll_stopped_at = performance.now();
      clear_timeouts();
      last_roll_value = pending_final_value;
      face_element.textContent = last_roll_value;
      lock_die_face();
      practice_rolls_completed += 1;
      const rolls_remaining = Math.max(
        practice_roll_limit - practice_rolls_completed,
        0
      );
      const formatted_result = this.render_result_text(
        trial.result_template,
        {
          value: last_roll_value,
          roll_number: practice_rolls_completed,
          roll_limit: practice_enabled ? practice_roll_limit : null,
          rolls_remaining: practice_enabled ? rolls_remaining : null,
        }
      );

      if (practice_enabled) {
        if (rolls_remaining > 0) {
          result_element.innerHTML =
            `${formatted_result} <p class = "mt-2"> You may roll again (${rolls_remaining} ` +
            `practice ${rolls_remaining === 1 ? 'roll' : 'rolls'} remaining) or click 'Continue' to proceed.</p>`;
          // Brief delay prevents a double-click from immediately starting a new roll.
          practice_reenable_timeout = window.setTimeout(() => {
            make_die_interactive();
            has_rolled = false;
            practice_reenable_timeout = null;
          }, PRACTICE_UNLOCK_DELAY_MS);
        } else {
          result_element.textContent =
            `${formatted_result} Practice complete. Click 'Continue' to proceed.`;
        }
      } else {
        result_element.textContent = formatted_result;
      }

      button_element.disabled = false;
      if (!practice_enabled || rolls_remaining === 0) {
        button_element.focus();
      }
    };

    const begin_roll = () => {
      if (is_rolling || has_rolled) {
        return;
      }
      previous_face_value = null;
      pending_final_value = get_next_result_value();
      is_rolling = true;
      die_click_count += 1;
      roll_started_at = performance.now();
      result_element.textContent = 'Rolling... click again to stop the die.';
      face_element.classList.add('jspsych_die_roll_face--active');
      button_element.disabled = true;
      clear_timeouts();
      cycle_face();
    };

    const handle_face_interaction = event => {
      const event_type = event?.type ?? 'unknown';

      if (
        (event_type === 'pointerdown' || event_type === 'click') &&
        typeof event?.button === 'number' &&
        event.button !== 0
      ) {
        return;
      }

      if (practice_enabled) {
        const now = performance.now();
        if (!is_rolling && last_face_interaction_at !== null) {
          const elapsed = now - last_face_interaction_at;
          if (elapsed < MIN_IDLE_CLICK_INTERVAL_MS) {
            event?.preventDefault();
            return;
          }
        }
        last_face_interaction_at = now;
      }

      if (is_rolling) {
        finalize_roll();
        return;
      }
      if (!has_rolled) {
        begin_roll();
        return;
      }
    };

    button_element.disabled = true;
    face_element.textContent = '';
    result_element.innerHTML = practice_enabled
      ? `<p>Practice rolling the die. You can complete up to ${practice_roll_limit}</p>` +
        `practice ${practice_roll_limit === 1 ? 'roll' : 'rolls'}. <p class = "mt-2"> Click the die to start, then click it again to stop. </p>`
      : 'Click the die to start, then click it again to lock in your number.';
    make_die_interactive();
    face_element.setAttribute('role', 'button');
    face_element.setAttribute(
      'aria-label',
      'Virtual die. Click to start and stop.'
    );

    const face_interaction_event =
      typeof window.PointerEvent === 'function' ? 'pointerdown' : 'click';
    face_element.addEventListener(face_interaction_event, handle_face_interaction);
    face_element.addEventListener('keydown', event => {
      if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        handle_face_interaction(event);
      }
    });

    const end_trial = () => {
      is_rolling = false;
      clear_timeouts();
      const trial_data = {
        roll: last_roll_value,
        rigged_value: sanitized_rigged_value,
        rigged: sanitized_rigged_value !== null,
        die_click_count: die_click_count,
        roll_started_after_ms: roll_started_at
          ? roll_started_at - start_time
          : null,
        roll_duration_ms:
          roll_started_at && roll_stopped_at
            ? roll_stopped_at - roll_started_at
            : null,
        rt: performance.now() - start_time,
        practice_trial: practice_enabled,
        practice_roll_limit: practice_enabled ? practice_roll_limit : null,
        practice_rolls_completed: practice_enabled
          ? practice_rolls_completed
          : null,
        practice_rolls_remaining: practice_enabled
          ? Math.max(practice_roll_limit - practice_rolls_completed, 0)
          : null,
      };
      display_element.innerHTML = '';
      this.jsPsych.finishTrial(trial_data);
    };

    button_element.addEventListener('click', () => {
      if (!button_element.disabled) {
        end_trial();
      }
    });
  }

  build_markup(prompt, button_label) {
    const prompt_html = prompt
      ? `<div class="jspsych_die_roll_prompt">${prompt}</div>`
      : '';
    return `
      <div class="jspsych_die_roll">
        ${prompt_html}
        <div class="jspsych_die_roll_face">?</div>
        <div class="jspsych_die_roll_result" aria-live="polite"></div>
        <button class="jspsych-btn jspsych_die_roll_button" type="button">${button_label}</button>
      </div>
    `;
  }

  get_random_value() {
    return this.jsPsych.randomization.sampleWithoutReplacement(
      [1, 2, 3, 4, 5, 6],
      1
    )[0];
  }

  get_rigged_value(value) {
    const numeric = Number(value);
    if (Number.isFinite(numeric) && numeric >= 1 && numeric <= 6) {
      return Math.round(numeric);
    }
    return null;
  }

  get_practice_roll_limit(value) {
    const numeric = Number(value);
    if (Number.isFinite(numeric) && numeric >= 1) {
      return Math.round(numeric);
    }
    return 5;
  }

  render_result_text(template, replacements) {
    if (typeof template !== 'string' || template.length === 0) {
      return '';
    }
    return Object.entries(replacements).reduce((text, [key, value]) => {
      if (value === null || value === undefined) {
        return text;
      }
      const pattern = new RegExp(`\\{\\{${key}\\}\\}`, 'gi');
      return text.replace(pattern, value);
    }, template);
  }
}

DieRollPlugin.info = info;

export default DieRollPlugin;
