import React, { useState } from "react";
import { __, sprintf } from "@wordpress/i18n";
import { Info, Plus, Trash2 } from "lucide-react";
import AjaxSelect from "./AjaxSelect";
import RangeSlider, { rangeColor } from "./RangeSlider";

/**
 * "Discounts and Freebies" screen: basis select, multi-point range slider,
 * and the per-range freebies/discount editors + summary list.
 */
const DiscountBuilder = ({ discounts, onChange, currencySymbol }) => {
	const isCount = discounts.basis === "total_count";
	const boundaries = discounts.boundaries;
	const ranges = discounts.ranges;
	const parsedMaxValue = Number(discounts.max_value);
	const maxValue = Number.isFinite(parsedMaxValue) && parsedMaxValue > 0 ? parsedMaxValue : 0;
	const hasMaxValue = maxValue > 0;
	// Draft text for the per-range "From" inputs — committed on blur/Enter so
	// mid-typing intermediate values never cascade and corrupt neighbors.
	const [drafts, setDrafts] = useState({});

	const scaleMax = maxValue;

	const formatPoint = (value) =>
		isCount
			? String(Math.round(value))
			: `${currencySymbol}${Number(value).toFixed(2).replace(/\.00$/, "")}`;

	const rangeBoundsLabel = (index) => {
		const from = index === 0 ? 0 : boundaries[index - 1];
		const to = index < boundaries.length ? boundaries[index] : null;
		if (to === null) {
			return sprintf(
				/* translators: %s: range start. */
				__("%s and above", "arraysubs"),
				formatPoint(from)
			);
		}
		return `${formatPoint(from)} – ${formatPoint(to)}`;
	};

	const emptyRange = () => ({ freebies: [], discount: { type: "none", amount: 0 } });

	/**
	 * Re-fit boundary points into a [0, nextMax] domain, keeping every point's
	 * rules attached to it. Points past the edge clamp onto it — leaving them
	 * outside would hide them from the slider while they kept discounting the
	 * storefront box — and points that collide after clamping drop with their
	 * range. Mirrors BoxConfig::sanitizeDiscounts() so the picker shows exactly
	 * what will be saved.
	 */
	const fitToDomain = (nextMax, sourceBoundaries, sourceRanges) => {
		const nextBoundaries = [];
		const nextRanges = [sourceRanges[0] || emptyRange()];
		// Source points are already ascending and clamping is monotonic, so the
		// result stays sorted.
		sourceBoundaries.forEach((boundary, index) => {
			const clamped = nextMax > 0 ? Math.min(boundary, nextMax) : boundary;
			if (clamped <= 0 || nextBoundaries.includes(clamped)) {
				return;
			}
			nextBoundaries.push(clamped);
			nextRanges.push(sourceRanges[index + 1] || emptyRange());
		});
		return { boundaries: nextBoundaries, ranges: nextRanges };
	};

	const setBasis = (basis) => {
		// Switching basis invalidates boundary units — round for counts.
		const roundedBoundaries =
			basis === "total_count"
				? boundaries.map((b) => Math.max(1, Math.round(b)))
				: [...boundaries];
		const nextMax =
			basis === "total_count" && maxValue > 0 ? Math.max(1, Math.round(maxValue)) : maxValue;
		// Rounding can push a point past a rounded-down max, so re-fit both.
		const fitted = fitToDomain(nextMax, roundedBoundaries, ranges);
		onChange({
			...discounts,
			basis,
			max_value: nextMax,
			boundaries: fitted.boundaries,
			ranges: fitted.ranges,
		});
	};

	const setMaxValue = (rawValue) => {
		onChange({ ...discounts, max_value: rawValue });
	};

	const normalizeMaxValue = (rawValue) => {
		const value = Number(rawValue);
		if (!Number.isFinite(value) || value <= 0) {
			setMaxValue("");
			return;
		}
		const nextMax = isCount ? Math.max(1, Math.round(value)) : Math.round(value * 100) / 100;
		const fitted = fitToDomain(nextMax, boundaries, ranges);
		onChange({
			...discounts,
			max_value: nextMax,
			boundaries: fitted.boundaries,
			ranges: fitted.ranges,
		});
	};

	const setBoundaries = (nextBoundaries) => {
		onChange({ ...discounts, boundaries: nextBoundaries });
	};

	const addPoint = () => {
		if (!hasMaxValue || boundaries.length >= 10) {
			return;
		}
		// New point: midpoint of the largest gap (including the tail gap).
		const edges = [0, ...boundaries, scaleMax];
		let bestGapIndex = 0;
		let bestGapSize = -1;
		for (let i = 0; i < edges.length - 1; i++) {
			const size = edges[i + 1] - edges[i];
			if (size > bestGapSize) {
				bestGapSize = size;
				bestGapIndex = i;
			}
		}
		let point = (edges[bestGapIndex] + edges[bestGapIndex + 1]) / 2;
		point = isCount ? Math.max(1, Math.round(point)) : Math.round(point * 100) / 100;
		if (boundaries.includes(point)) {
			return;
		}

		const nextBoundaries = [...boundaries, point].sort((a, b) => a - b);
		const insertIndex = nextBoundaries.indexOf(point);
		const nextRanges = [...ranges];
		// The split range keeps its rules; the new segment starts clean.
		nextRanges.splice(insertIndex + 1, 0, {
			freebies: [],
			discount: { type: "none", amount: 0 },
		});
		onChange({ ...discounts, boundaries: nextBoundaries, ranges: nextRanges });
	};

	const removePoint = (index) => {
		const nextBoundaries = boundaries.filter((b, i) => i !== index);
		const nextRanges = [...ranges];
		// Merging: drop the range that started at this boundary.
		nextRanges.splice(index + 1, 1);
		onChange({ ...discounts, boundaries: nextBoundaries, ranges: nextRanges });
	};

	const updateRange = (index, updates) => {
		onChange({
			...discounts,
			ranges: ranges.map((range, i) => (i === index ? { ...range, ...updates } : range)),
		});
	};

	return (
		<div className="arraysubs-box-discounts">
			<div className="arraysubs-box-section__head">
				<h3 className="arraysubs-box-section__title">
					{__("Discounts & Freebies", "arraysubs")}
				</h3>
				<p className="arraysubs-box-section__desc">
					{__(
						"Reward bigger boxes. Split the scale into ranges, then give each range its own discount and free gifts. Leave every range on “No discount” with no freebies if you do not want a tiered offer.",
						"arraysubs"
					)}
				</p>
			</div>

			<div className="arraysubs-box-alert">
				<Info size={16} className="arraysubs-box-alert__icon" />
				<div className="arraysubs-box-alert__body">
					<strong>{__("How ranges are matched", "arraysubs")}</strong>
					<ul>
						<li>
							{__(
								"Ranges start at 0 and each one runs up to the next point: with points at 40 and 60 you get 0 – 40, 40 – 60, and 60 and above. A box lands in the range its value is greater than or equal to.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"The last range is open-ended. If the picker covers up to 100 and the last point is 60, a customer whose box comes to 300 still lands in that final range.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"Only one range ever applies — the one the box falls into. Its discount and its freebies are given together, and ranges never stack.",
								"arraysubs"
							)}
						</li>
						<li>
							{__(
								"The maximum below only sets how far the picker is drawn. It never caps what a customer may put in the box — per-element limits on the Box Steps screen do that.",
								"arraysubs"
							)}
						</li>
					</ul>
				</div>
			</div>

			<div className="arraysubs-box-field-grid arraysubs-box-field-grid--discount-controls">
				<label className="arraysubs-box-field-item">
					<span>{__("Ranges Based On", "arraysubs")}</span>
					<select value={discounts.basis} onChange={(event) => setBasis(event.target.value)}>
						<option value="total_value">{__("Total Value", "arraysubs")}</option>
						<option value="total_count">{__("Total Count", "arraysubs")}</option>
					</select>
					<small>
						{isCount
							? __("Ranges apply to the number of items in the box.", "arraysubs")
							: __("Ranges apply to the box subtotal before discount.", "arraysubs")}
					</small>
				</label>
				<label className="arraysubs-box-field-item">
					<span>
						{isCount
							? __("Max items to configure", "arraysubs")
							: __("Max amount to configure", "arraysubs")}
					</span>
					<input
						type="number"
						required
						min={isCount ? 1 : 0.01}
						step={isCount ? 1 : 0.01}
						value={discounts.max_value || ""}
						onChange={(event) => setMaxValue(event.target.value)}
						onBlur={(event) => normalizeMaxValue(event.target.value)}
					/>
					<small>
						{isCount
							? __(
									"Enter the highest item count the range picker should cover. This is the drawing scale only, not a limit on the customer \u2014 lowering it pulls any point above it back to the edge.",
									"arraysubs"
							  )
							: __(
									"Enter the highest box subtotal the range picker should cover. This is the drawing scale only, not a limit on the customer \u2014 lowering it pulls any point above it back to the edge.",
									"arraysubs"
							  )}
					</small>
				</label>
				<div className="arraysubs-box-field-item arraysubs-box-field-item--actions">
					<span className="arraysubs-box-field-item__control-spacer" aria-hidden="true">
						{"\u00a0"}
					</span>
					<button
						type="button"
						className="button"
						onClick={addPoint}
						disabled={!hasMaxValue || boundaries.length >= 10}
					>
						<Plus size={14} />
						{__("Add Range Point", "arraysubs")}
					</button>
					<small>
						{boundaries.length >= 10
							? __("Maximum of 10 range points reached.", "arraysubs")
							: sprintf(
									/* translators: %d: number of range points already placed. */
									__(
										"Splits the widest gap in two. %d of 10 points placed \u2014 each point starts a new range.",
										"arraysubs"
									),
									boundaries.length
							  )}
					</small>
				</div>
			</div>

			{hasMaxValue && (
				<RangeSlider
					boundaries={boundaries}
					onChange={setBoundaries}
					scaleMax={scaleMax}
					isCount={isCount}
					currencySymbol={currencySymbol}
				/>
			)}

			<div className="arraysubs-box-ranges" hidden={!hasMaxValue}>
				{ranges.map((range, index) => (
					// eslint-disable-next-line react/no-array-index-key
					<div className="arraysubs-box-range-card" key={index}>
						<div className="arraysubs-box-range-card__header">
							<span
								className="arraysubs-box-range-card__swatch"
								style={{ background: rangeColor(index) }}
							/>
							<strong>{rangeBoundsLabel(index)}</strong>
							{index > 0 && (
								<span className="arraysubs-box-range-card__point">
									<label>
										{__("From", "arraysubs")}
										<input
											type="number"
											min={isCount ? 1 : 0.01}
											max={scaleMax}
											step={isCount ? 1 : 0.01}
											title={__(
												"Where this range starts. Committed when you press Enter or leave the field, and kept between its neighbouring points.",
												"arraysubs"
											)}
											value={
												drafts[index - 1] !== undefined
													? drafts[index - 1]
													: boundaries[index - 1]
											}
											onChange={(event) =>
												setDrafts((prev) => ({ ...prev, [index - 1]: event.target.value }))
											}
											onKeyDown={(event) => {
												if (event.key === "Enter") {
													event.currentTarget.blur();
												}
											}}
											onBlur={(event) => {
												const bi = index - 1;
												setDrafts((prev) => {
													const nextDraft = { ...prev };
													delete nextDraft[bi];
													return nextDraft;
												});
												const raw = event.target.value;
												if (raw === "") {
													return; // no-op: keep the existing boundary
												}
												const step = isCount ? 1 : 0.01;
												let value = Number(raw);
												if (Number.isNaN(value)) {
													return;
												}
												// Clamp strictly between neighbors so a single edit can
												// never collide with or leapfrog adjacent points.
												const lower = bi > 0 ? boundaries[bi - 1] + step : step;
												const upper =
													bi < boundaries.length - 1 ? boundaries[bi + 1] - step : scaleMax;
												value = Math.min(Math.max(value, lower), Math.max(lower, upper));
												value = isCount ? Math.round(value) : Math.round(value * 100) / 100;
												if (value === boundaries[bi]) {
													return;
												}
												const next = [...boundaries];
												next[bi] = value;
												setBoundaries(next);
											}}
										/>
									</label>
									<button
										type="button"
										className="button arraysubs-box-icon-btn arraysubs-box-icon-btn--danger"
										onClick={() => removePoint(index - 1)}
										aria-label={__("Remove range point", "arraysubs")}
										title={__(
											"Remove this point. Its range merges into the one above it and its discount and freebies are dropped.",
											"arraysubs"
										)}
									>
										<Trash2 size={13} />
									</button>
								</span>
							)}
						</div>

						<div className="arraysubs-box-field-grid">
							<div className="arraysubs-box-field-item arraysubs-box-field-item--wide">
								<span>{__("Freebies", "arraysubs")}</span>
								<AjaxSelect
									value={range.freebies}
									onChange={(freebies) => updateRange(index, { freebies })}
									endpoint="products"
									multiple
									placeholder={__("Search freebie products…", "arraysubs")}
								/>
								<small>
									{__(
										"Added to the order free when the box lands in this range. Freebies are not tied to the box billing cycle, so any purchasable product can be used.",
										"arraysubs"
									)}
								</small>
							</div>
							<label className="arraysubs-box-field-item">
								<span>{__("Discount", "arraysubs")}</span>
								<select
									value={range.discount.type}
									onChange={(event) =>
										updateRange(index, {
											discount: { ...range.discount, type: event.target.value },
										})
									}
								>
									<option value="none">{__("No discount", "arraysubs")}</option>
									<option value="fixed">{__("Fixed amount", "arraysubs")}</option>
									<option value="percent">{__("Percentage", "arraysubs")}</option>
								</select>
								<small>
									{__(
										"Taken off the box subtotal for this range only.",
										"arraysubs"
									)}
								</small>
							</label>
							{range.discount.type !== "none" && (
								<label className="arraysubs-box-field-item">
									<span>
										{range.discount.type === "percent"
											? __("Percent Off", "arraysubs")
											: sprintf(
													/* translators: %s: currency symbol. */
													__("Amount Off (%s)", "arraysubs"),
													currencySymbol
											  )}
									</span>
									<input
										type="number"
										min={0}
										max={range.discount.type === "percent" ? 100 : undefined}
										step="0.01"
										value={range.discount.amount}
										onChange={(event) => {
											let amount = Number(event.target.value);
											if (Number.isNaN(amount) || amount < 0) {
												amount = 0;
											}
											if (range.discount.type === "percent") {
												amount = Math.min(amount, 100);
											}
											updateRange(index, {
												discount: { ...range.discount, amount },
											});
										}}
									/>
									<small>
										{range.discount.type === "percent"
											? __(
													"Capped at 100%. Leave at 0 to give this range no discount.",
													"arraysubs"
											  )
											: __(
													"Never takes the box below zero. Leave at 0 to give this range no discount.",
													"arraysubs"
											  )}
									</small>
								</label>
							)}
						</div>
					</div>
				))}
			</div>

			<div className="arraysubs-box-range-summary" hidden={!hasMaxValue}>
				<div className="arraysubs-box-range-summary__title">
					{__("Range Summary", "arraysubs")}
				</div>
				<table className="arraysubs-box-summary-table">
					<thead>
						<tr>
							<th>{__("Range", "arraysubs")}</th>
							<th>{__("Discount", "arraysubs")}</th>
							<th>{__("Freebies", "arraysubs")}</th>
						</tr>
					</thead>
					<tbody>
						{ranges.map((range, index) => (
							// eslint-disable-next-line react/no-array-index-key
							<tr key={index}>
								<td>
									<span className="arraysubs-box-range-summary__range-label">
										<span
											className="arraysubs-box-range-card__swatch"
											style={{ background: rangeColor(index) }}
										/>
										<span>{rangeBoundsLabel(index)}</span>
									</span>
								</td>
								<td>
									{range.discount.type === "none" && "—"}
									{range.discount.type === "percent" && `${range.discount.amount}%`}
									{range.discount.type === "fixed" &&
										`${currencySymbol}${Number(range.discount.amount).toFixed(2)}`}
								</td>
								<td>
									{range.freebies.length === 0
										? "—"
										: sprintf(
												/* translators: %d: number of freebie products. */
												__("%d product(s)", "arraysubs"),
												range.freebies.length
										  )}
								</td>
							</tr>
						))}
					</tbody>
				</table>
			</div>
		</div>
	);
};

export default DiscountBuilder;
