import msvcrt
import sys
import math

items = []
diners = []

# ANSI color codes for terminal output
GREEN = "\033[1;92m"
RESET = "\033[0m"

def colored_input(prompt):
	print(prompt, end="", flush=True)
	print(GREEN, end="", flush=True)

	characters = []
	while True:
		char = msvcrt.getwch()

		if char == "\x03":
			print(RESET, end="", flush=True)
			raise KeyboardInterrupt

		if char in ("\r", "\n"):
			print(RESET)
			return "".join(characters)

		if char == "\x08":
			if characters:
				characters.pop()
				sys.stdout.write("\b \b")
				sys.stdout.flush()
			continue

		if char in ("\x00", "\xe0"):
			msvcrt.getwch()
			continue

		characters.append(char)
		sys.stdout.write(char)
		sys.stdout.flush()

def section(title):
	print("\n" + "=" * 60)
	print(title)
	print("=" * 60)


def list_diners(diners):
	if not diners:
		print("No diners on the list.")
		return
	for i, diner in enumerate(diners, start=1):
		print(f"{i}. {diner}")


def choose_diner_index(diners, prompt):
	while True:
		choice = colored_input(prompt).strip()
		if not choice.isdigit():
			print("Enter a valid number from the list.")
			continue
		index = int(choice)
		if index < 1 or index > len(diners):
			print("Number out of range.")
			continue
		return index - 1


def choose_sharers(diners):
	# Choose one or more diners by number (e.g. 1 or 1,3).
	while True:
		print("Who is sharing this item?")
		for i, diner in enumerate(diners, start=1):
			print(f"{i}. {diner}")
		raw = colored_input("Choose diner number(s), comma-separated: ").strip()

		if raw == "":
			print("Please choose at least one diner.")
			continue

		parts = [part.strip() for part in raw.split(",") if part.strip() != ""]
		if not parts:
			print("Please choose at least one diner.")
			continue

		indexes = []
		valid = True
		for part in parts:
			if not part.isdigit():
				valid = False
				break
			index = int(part)
			if index < 1 or index > len(diners):
				valid = False
				break
			if index not in indexes:
				indexes.append(index)

		if not valid:
			print("Enter valid diner numbers from the list (example: 1,3).")
			continue

		return [diners[index - 1] for index in indexes]


def list_items(items):
	if not items:
		print("No items on the list.")
		return
	for i, item in enumerate(items, start=1):
		shared_by = ", ".join(item["sharers"])
		if item["category"] == "Appetizer":
			detail = "Appetizer, split across table"
		elif item["category"] == "Ordered":
			detail = f"Ordered by: {shared_by}"
		else:
			detail = f"Shared by: {shared_by}"
		print(f"{i}. {item['name']}: ${item['price']:.2f} ({detail})")


def choose_item_index(items, prompt):
	while True:
		choice = colored_input(prompt).strip()
		if not choice.isdigit():
			print("Enter a valid number from the list.")
			continue
		index = int(choice)
		if index < 1 or index > len(items):
			print("Number out of range.")
			continue
		return index - 1


def prompt_item_details(diners, item=None):
	while True:
		default_name = f" [{item['name']}]" if item else ""
		item_name = colored_input(f"Item name{default_name}: ").strip()
		if item_name == "" and item:
			item_name = item["name"]
		if item_name == "":
			print("Item name cannot be blank.")
			continue
		break

	while True:
		default_app = "y" if (item and item["category"] == "Appetizer") else "n"
		is_appetizer = colored_input(f"Is this an appetizer for the whole table? (y/n) [{default_app}]: ").strip().lower()
		if is_appetizer == "":
			is_appetizer = default_app
		if is_appetizer in ("n", "no"):
			is_appetizer = "n"
			break
		if is_appetizer in ("y", "yes"):
			break
		print("Please enter y or n.")

	if is_appetizer in ("y", "yes"):
		sharers = list(diners)
		category = "Appetizer"
	else:
		sharers = choose_sharers(diners)
		if len(sharers) > 1:
			category = "Shared"
		else:
			category = "Ordered"

	while True:
		default_price = f" [{item['price']:.2f}]" if item else ""
		entry = colored_input(f"Price for {item_name}{default_price}: ").strip()
		if entry == "" and item:
			price = item["price"]
			break
		try:
			price = float(entry)
			if price < 0:
				print("Price cannot be negative.")
				continue
			break
		except ValueError:
			print("Please enter a valid number.")

	return {"name": item_name, "price": price, "sharers": sharers, "category": category}


section("DINER SETUP")
print("Enter diner names first.")
print("Press Enter on a blank name when done.")

while True:
	name = colored_input("Diner name: ").strip()
	if name == "":
		break
	if name.lower() in (d.lower() for d in diners):
		print("That diner is already on the list.")
		continue
	diners.append(name)
	print(f"Added diner: {name}")

if not diners:
	print("No diners entered.")
	raise SystemExit

section("DINER REVIEW")
print("Review diners before entering items.")
print("Options: [A]dd  [E]dit  [D]elete  [C]ontinue")

while True:
	print("\nCurrent diners:")
	list_diners(diners)

	action = colored_input("Choose action (A/E/D/C): ").strip().lower()

	if action in ("c", "continue"):
		if not diners:
			print("Add at least one diner before continuing.")
			continue
		break

	if action in ("a", "add"):
		new_name = colored_input("New diner name: ").strip()
		if new_name == "":
			print("Name cannot be blank.")
			continue
		if new_name.lower() in (d.lower() for d in diners):
			print("That diner is already on the list.")
			continue
		diners.append(new_name)
		print(f"Added diner: {new_name}")
		continue

	if action in ("e", "edit"):
		if not diners:
			print("No diners to edit.")
			continue
		index = choose_diner_index(diners, "Diner number to edit: ")
		old_name = diners[index]
		updated_name = colored_input(f"New name for {old_name}: ").strip()
		if updated_name == "":
			print("Name cannot be blank.")
			continue
		if any(d.lower() == updated_name.lower() for i, d in enumerate(diners) if i != index):
			print("That diner is already on the list.")
			continue
		diners[index] = updated_name
		print(f"Updated diner: {old_name} -> {updated_name}")
		continue

	if action in ("d", "delete"):
		if not diners:
			print("No diners to delete.")
			continue
		index = choose_diner_index(diners, "Diner number to delete: ")
		removed = diners.pop(index)
		print(f"Deleted diner: {removed}")
		continue

	print("Enter A, E, D, or C.")

section("ITEM ENTRY")
print("Enter each item name and price.")
print("Press Enter on a blank item name when done.")

item_number = 1
while True:
	print(f"\nItem #{item_number}")
	print("-" * 20)
	item_name = colored_input("Item name: ").strip()
	if item_name == "":
		break

	while True:
		is_appetizer = colored_input("Is this an appetizer for the whole table? (y/n) [n]: ").strip().lower()
		if is_appetizer in ("", "n", "no"):
			is_appetizer = "n"
			break
		if is_appetizer in ("y", "yes"):
			break
		print("Please enter y or n.")

	if is_appetizer in ("y", "yes"):
		sharers = list(diners)
		category = "Appetizer"
	else:
		sharers = choose_sharers(diners)
		if len(sharers) > 1:
			category = "Shared"
		else:
			category = "Ordered"

	while True:
		entry = colored_input(f"Price for {item_name}: ").strip()
		try:
			price = float(entry)
			if price < 0:
				print("Price cannot be negative.")
				continue
			items.append({"name": item_name, "price": price, "sharers": sharers, "category": category})
			if category == "Appetizer":
				print(f"Added: {item_name} (${price:.2f}) as table appetizer")
			else:
				print(f"Added: {item_name} (${price:.2f}) shared by {', '.join(sharers)}")
			item_number += 1
			break
		except ValueError:
			print("Please enter a valid number.")

section("ITEM REVIEW")
print("Review menu items before calculating tip.")
print("Options: [A]dd  [E]dit  [D]elete  [C]ontinue")

while True:
	print("\nCurrent items:")
	list_items(items)

	action = colored_input("Choose action (A/E/D/C): ").strip().lower()

	if action in ("c", "continue"):
		if not items:
			print("Add at least one item before continuing.")
			continue
		break

	if action in ("a", "add"):
		new_item = prompt_item_details(diners)
		items.append(new_item)
		if new_item["category"] == "Appetizer":
			print(f"Added: {new_item['name']} (${new_item['price']:.2f}) as table appetizer")
		else:
			print(f"Added: {new_item['name']} (${new_item['price']:.2f}) shared by {', '.join(new_item['sharers'])}")
		continue

	if action in ("e", "edit"):
		if not items:
			print("No items to edit.")
			continue
		index = choose_item_index(items, "Item number to edit: ")
		old_name = items[index]["name"]
		print(f"Editing item: {old_name}")
		updated_item = prompt_item_details(diners, items[index])
		items[index] = updated_item
		print(f"Updated item: {old_name} -> {updated_item['name']}")
		continue

	if action in ("d", "delete"):
		if not items:
			print("No items to delete.")
			continue
		index = choose_item_index(items, "Item number to delete: ")
		removed = items.pop(index)
		print(f"Deleted item: {removed['name']}")
		continue

	print("Enter A, E, D, or C.")

if not items:
	print("No items entered.")
else:
	section("TIP")
	subtotal = sum(item["price"] for item in items)
	print(f"Current subtotal: ${subtotal:.2f}")

	while True:
		tip_input = colored_input("What percent tip would you like to leave? ").strip().replace("%", "")
		try:
			tip_percent = float(tip_input)
			if tip_percent < 0:
				print("Tip percent cannot be negative.")
				continue
			break
		except ValueError:
			print("Please enter a valid tip percentage.")

	tip_amount = round(subtotal * (tip_percent / 100))
	total_bill = subtotal + tip_amount

	section("ITEMIZED BILL")
	for item in items:
		shared_by = ", ".join(item["sharers"])
		if item["category"] == "Appetizer":
			print(f"- {item['name']}: ${item['price']:.2f} (Appetizer, split across table)")
		else:
			print(f"- {item['name']}: ${item['price']:.2f} (Shared by: {shared_by})")

	# Calculate per-person subtotals
	person_subtotals = {person: 0 for person in diners}
	for item in items:
		share_cost = item["price"] / len(item["sharers"])
		for person in item["sharers"]:
			person_subtotals[person] += share_cost

	# Calculate per-person tip share (proportional)
	person_tips = {}
	for person, person_subtotal in person_subtotals.items():
		if subtotal > 0:
			person_tips[person] = tip_amount * (person_subtotal / subtotal)
		else:
			person_tips[person] = 0

	section("PER-PERSON TOTALS")
	rounded_totals = {}
	for person in person_subtotals:
		owed = person_subtotals[person] + person_tips[person]
		rounded_owed = math.ceil(owed)
		rounded_totals[person] = rounded_owed
		print(f"{person}: ${rounded_owed:.2f} (Subtotal: ${person_subtotals[person]:.2f}, Tip: ${person_tips[person]:.2f})")

	section("FINAL TOTAL")
	actual_total = sum(rounded_totals.values())
	actual_tip = actual_total - subtotal
	actual_tip_percent = (actual_tip / subtotal * 100) if subtotal > 0 else 0
	print(f"Check subtotal: ${subtotal:.2f}")
	print(f"Tip ({actual_tip_percent:.1f}%): ${actual_tip:.2f}")
	print(f"Final bill: ${actual_total:.2f}")
